Ta strona opiera się na koncepcjach z Strukturyzacji reguł bezpieczeństwa i Pisania warunków dla reguł bezpieczeństwa , aby wyjaśnić, w jaki sposób można używać reguł bezpieczeństwa Cloud Firestore do tworzenia reguł, które pozwalają klientom wykonywać operacje na niektórych polach dokumentu, ale nie na innych.
Może się zdarzyć, że będziesz chciał kontrolować zmiany w dokumencie nie na poziomie dokumentu, ale na poziomie pola.
Na przykład możesz zezwolić klientowi na utworzenie lub zmianę dokumentu, ale nie pozwolić mu na edycję niektórych pól w tym dokumencie. Możesz też wymusić, aby każdy dokument zawsze tworzony przez klienta zawierał określony zestaw pól. W tym przewodniku opisano, jak wykonać niektóre z tych zadań za pomocą reguł bezpieczeństwa Cloud Firestore.
Zezwalanie na dostęp do odczytu tylko dla określonych pól
Odczyty w Cloud Firestore wykonywane są na poziomie dokumentu. Albo pobierasz cały dokument, albo nie pobierasz niczego. Nie ma możliwości odzyskania częściowego dokumentu. Niemożliwe jest użycie samych reguł bezpieczeństwa, aby uniemożliwić użytkownikom czytanie określonych pól w dokumencie.
Jeśli w dokumencie znajdują się pewne pola, które chcesz ukryć przed niektórymi użytkownikami, najlepszym sposobem byłoby umieszczenie ich w osobnym dokumencie. Możesz na przykład rozważyć utworzenie dokumentu w private
podkolekcji w następujący sposób:
/pracownicy/{emp_id}
name: "Alice Hamilton",
department: 461,
start_date: <timestamp>
/employees/{emp_id}/private/finanse
salary: 80000,
bonus_mult: 1.25,
perf_review: 4.2
Następnie możesz dodać reguły bezpieczeństwa, które mają różne poziomy dostępu dla obu kolekcji. W tym przykładzie używamy niestandardowych oświadczeń autoryzacyjnych , aby powiedzieć, że tylko użytkownicy z role
niestandardowych oświadczeń autoryzacyjnych równą Finance
mogą przeglądać informacje finansowe pracownika.
service cloud.firestore {
match /databases/{database}/documents {
// Allow any logged in user to view the public employee data
match /employees/{emp_id} {
allow read: if request.resource.auth != null
// Allow only users with the custom auth claim of "Finance" to view
// the employee's financial data
match /private/finances {
allow read: if request.resource.auth &&
request.resource.auth.token.role == 'Finance'
}
}
}
}
Ograniczanie pól podczas tworzenia dokumentu
Cloud Firestore jest bezschematowy, co oznacza, że na poziomie bazy danych nie ma żadnych ograniczeń dotyczących pól, które zawiera dokument. Chociaż ta elastyczność może ułatwić programowanie, będą chwile, kiedy będziesz chciał mieć pewność, że klienci będą mogli tworzyć tylko dokumenty zawierające określone pola lub nie zawierające innych pól.
Możesz utworzyć te reguły, sprawdzając metodę keys
obiektu request.resource.data
. To jest lista wszystkich pól, które klient próbuje zapisać w nowym dokumencie. Łącząc ten zestaw pól z funkcjami takimi jak hasOnly()
lub hasAny()
, możesz dodać logikę ograniczającą typy dokumentów, które użytkownik może dodać do Cloud Firestore.
Wymaganie określonych pól w nowych dokumentach
Załóżmy, że chcesz się upewnić, że wszystkie dokumenty utworzone w kolekcji restaurant
zawierają co najmniej pole name
, location
i city
. Możesz to zrobić, wywołując funkcję hasAll()
na liście kluczy w nowym dokumencie.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document contains a name
// location, and city field
match /restaurant/{restId} {
allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
}
}
}
Pozwala to na tworzenie restauracji również z innymi polami, ale gwarantuje, że wszystkie dokumenty utworzone przez klienta będą zawierały przynajmniej te trzy pola.
Zabranianie określonych pól w nowych dokumentach
Podobnie możesz uniemożliwić klientom tworzenie dokumentów zawierających określone pola, używając hasAny()
w odniesieniu do listy zabronionych pól. Ta metoda ma wartość true, jeśli dokument zawiera którekolwiek z tych pól, więc prawdopodobnie chcesz zanegować wynik, aby zabronić niektórych pól.
Na przykład w poniższym przykładzie klientom nie wolno tworzyć dokumentu zawierającego pole average_score
lub rating_count
, ponieważ pola te zostaną dodane później przez wywołanie serwera.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document does *not*
// contain an average_score or rating_count field.
match /restaurant/{restId} {
allow create: if (!request.resource.data.keys().hasAny(
['average_score', 'rating_count']));
}
}
}
Tworzenie listy dozwolonych pól dla nowych dokumentów
Zamiast zabraniać niektórych pól w nowych dokumentach, możesz utworzyć listę tylko tych pól, które są wyraźnie dozwolone w nowych dokumentach. Następnie możesz użyć funkcji hasOnly()
, aby upewnić się, że wszystkie nowe utworzone dokumenty zawierają tylko te pola (lub podzbiór tych pól), a nie inne.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document doesn't contain
// any fields besides the ones listed below.
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
Łączenie pól wymaganych i opcjonalnych
Możesz połączyć operacje hasAll
i hasOnly
w regułach bezpieczeństwa, aby wymagać niektórych pól i zezwalać na inne. Na przykład ten przykład wymaga, aby wszystkie nowe dokumenty zawierały pola name
, location
i city
, a opcjonalnie dopuszcza pola address
, hours
i cuisine
.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document has a name,
// location, and city field, and optionally address, hours, or cuisine field
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
(request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
W prawdziwym scenariuszu możesz chcieć przenieść tę logikę do funkcji pomocniczej, aby uniknąć duplikowania kodu i łatwiej połączyć pola opcjonalne i wymagane w jedną listę, na przykład:
service cloud.firestore {
match /databases/{database}/documents {
function verifyFields(required, optional) {
let allAllowedFields = required.concat(optional);
return request.resource.data.keys().hasAll(required) &&
request.resource.data.keys().hasOnly(allAllowedFields);
}
match /restaurant/{restId} {
allow create: if verifyFields(['name', 'location', 'city'],
['address', 'hours', 'cuisine']);
}
}
}
Ograniczanie pól podczas aktualizacji
Powszechną praktyką bezpieczeństwa jest zezwalanie klientom na edycję tylko niektórych pól, a innych nie. Nie można tego osiągnąć wyłącznie poprzez przeglądanie listy request.resource.data.keys()
opisanej w poprzedniej sekcji, ponieważ ta lista reprezentuje kompletny dokument, tak jak wyglądałby po aktualizacji, a zatem zawierałby pola, których nie zrobił klient zmiana.
Jeśli jednak chcesz użyć funkcji diff()
, możesz porównać request.resource.data
z obiektem resource.data
, który reprezentuje dokument w bazie danych przed aktualizacją. Spowoduje to utworzenie obiektu mapDiff
, który jest obiektem zawierającym wszystkie zmiany pomiędzy dwiema różnymi mapami.
Wywołując metodę affectedKeys()
w tym mapDiff, możesz uzyskać zestaw pól, które zostały zmienione podczas edycji. Następnie możesz użyć funkcji takich jak hasOnly()
lub hasAny()
, aby upewnić się, że ten zestaw zawiera (lub nie) określone elementy.
Zapobieganie zmianie niektórych pól
Używając metody hasAny()
na zestawie wygenerowanym przez affectedKeys()
i następnie negując wynik, możesz odrzucić każde żądanie klienta próbującego zmienić pola, których nie chcesz zmieniać.
Możesz na przykład zezwolić klientom na aktualizowanie informacji o restauracji, ale nie zmieniać ich średniego wyniku ani liczby recenzji.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow the client to update a document only if that document doesn't
// change the average_score or rating_count fields
allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['average_score', 'rating_count']));
}
}
}
Zezwalanie na zmianę tylko niektórych pól
Zamiast określać pola, których nie chcesz zmieniać, możesz także użyć funkcji hasOnly()
w celu określenia listy pól, które chcesz zmienić. Jest to ogólnie uważane za bezpieczniejsze, ponieważ zapisy w nowych polach dokumentu są domyślnie zabronione, dopóki wyraźnie nie zezwolisz na to w regułach bezpieczeństwa.
Na przykład, zamiast blokować pola average_score
i rating_count
, możesz utworzyć reguły bezpieczeństwa, które pozwolą klientom zmieniać tylko name
, location
, city
, address
, hours
i cuisine
.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow a client to update only these 6 fields in a document
allow update: if (request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
Oznacza to, że jeśli w jakiejś przyszłej wersji aplikacji dokumenty restauracji będą zawierać pole telephone
, próby edycji tego pola nie powiodą się, dopóki nie wrócisz i nie dodasz tego pola do listy hasOnly()
w regułach bezpieczeństwa.
Egzekwowanie typów pól
Innym skutkiem braku schematu Cloud Firestore jest brak egzekwowania na poziomie bazy danych tego, jakie typy danych mogą być przechowywane w określonych polach. Można to jednak wymusić w regułach bezpieczeństwa za pomocą operatora is
.
Na przykład poniższa reguła bezpieczeństwa wymusza, aby pole score
recenzji było liczbą całkowitą, headline
, content
i author_name
były ciągami znaków, a review_date
była sygnaturą czasową.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if (request.resource.data.score is int &&
request.resource.data.headline is string &&
request.resource.data.content is string &&
request.resource.data.author_name is string &&
request.resource.data.review_date is timestamp
);
}
}
}
}
Prawidłowe typy danych dla is
is to bool
, bytes
, float
, int
, list
, latlng
, number
, path
, map
, string
i timestamp
. Operator is
obsługuje także typy danych constraint
, duration
, set
i map_diff
, ale ponieważ są one generowane przez sam język reguł bezpieczeństwa, a nie generowane przez klientów, rzadko używa się ich w większości praktycznych zastosowań.
typy danych list
i map
nie obsługują argumentów ogólnych ani typów. Innymi słowy, możesz użyć reguł bezpieczeństwa, aby wymusić, aby określone pole zawierało listę lub mapę, ale nie możesz wymusić, aby pole zawierało listę wszystkich liczb całkowitych lub wszystkich ciągów znaków.
Podobnie możesz użyć reguł bezpieczeństwa, aby wymusić wartości typu dla określonych wpisów na liście lub mapie (używając odpowiednio notacji nawiasów lub nazw kluczy), ale nie ma skrótu, aby wymusić typy danych wszystkich elementów na mapie lub liście pod adresem raz.
Na przykład poniższe reguły zapewniają, że pole tags
w dokumencie zawiera listę, a pierwszy wpis jest ciągiem znaków. Zapewnia również, że pole product
zawiera mapę, która z kolei zawiera nazwę produktu będącą ciągiem znaków oraz ilość będącą liczbą całkowitą.
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderId} {
allow create: if request.resource.data.tags is list &&
request.resource.data.tags[0] is string &&
request.resource.data.product is map &&
request.resource.data.product.name is string &&
request.resource.data.product.quantity is int
}
}
}
}
Typy pól muszą być wymuszane zarówno podczas tworzenia, jak i aktualizowania dokumentu. Dlatego warto rozważyć utworzenie funkcji pomocniczej, którą można wywołać zarówno w sekcjach tworzenia, jak i aktualizacji reguł bezpieczeństwa.
service cloud.firestore {
match /databases/{database}/documents {
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp;
}
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
}
}
}
}
Wymuszanie typów dla pól opcjonalnych
Należy pamiętać, że wywołanie request.resource.data.foo
w dokumencie, w którym foo
nie istnieje, powoduje błąd i dlatego każda reguła bezpieczeństwa wykonująca to wywołanie odrzuci żądanie. Możesz poradzić sobie z tą sytuacją, używając metody get
na request.resource.data
. Metoda get
umożliwia podanie domyślnego argumentu dla pola, które pobierasz z mapy, jeśli to pole nie istnieje.
Na przykład, jeśli dokumenty recenzji zawierają również opcjonalne pole photo_url
i opcjonalne pole tags
, dla których chcesz sprawdzić, czy są to odpowiednio ciągi znaków i listy, możesz to osiągnąć, przepisując funkcję reviewFieldsAreValidTypes
na mniej więcej następującą:
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp &&
docData.get('photo_url', '') is string &&
docData.get('tags', []) is list;
}
To odrzuca dokumenty, w których istnieją tags
, ale nie są listą, jednocześnie zezwalając na dokumenty, które nie zawierają pola tags
(lub photo_url
).
Częściowe zapisy nigdy nie są dozwolone
Ostatnia uwaga na temat reguł bezpieczeństwa Cloud Firestore jest taka, że albo pozwalają klientowi na wprowadzenie zmian w dokumencie, albo odrzucają całą edycję. Nie można utworzyć reguł bezpieczeństwa, które akceptują zapisy w niektórych polach dokumentu i odrzucają inne w tej samej operacji.