Testowanie reguł zabezpieczeń Cloud Firestore

Podczas tworzenia aplikacji możesz zablokować dostęp do bazy danych Cloud Firestore. Zanim jednak to zrobisz, musisz mieć bardziej szczegółowe informacjeCloud Firestore Security Rules. Za pomocą Cloud Firestore emulatora możesz nie tylko tworzyć prototypy i testować ogólne funkcje i działanie aplikacji, ale też pisać testy jednostkowe, które sprawdzają działanie Cloud Firestore Security Rules.

Krótkie wprowadzenie

W przypadku kilku podstawowych przypadków testowych z prostymi regułami wypróbuj przykładowy przewodnik.

Zrozumienie Cloud Firestore Security Rules

Wdrażaj Firebase AuthenticationCloud Firestore Security Rules w przypadku bezserwerowego uwierzytelniania, autoryzacji i weryfikacji danych, gdy używasz mobilnych i internetowych bibliotek klienta.

Cloud Firestore Security Rules obejmują 2 elementy:

  1. match instrukcja identyfikująca dokumenty w bazie danych;
  2. allow wyrażenie, które kontroluje dostęp do tych dokumentów.

Firebase Authentication weryfikuje dane logowania użytkowników i stanowi podstawę systemów dostępu opartych na użytkownikach i rolach.

Każde żądanie bazy danych z Cloud Firestorebiblioteki klienta mobilnego lub internetowego jest sprawdzane pod kątem reguł zabezpieczeń przed odczytaniem lub zapisaniem jakichkolwiek danych. Jeśli reguły odmawiają dostępu do którejkolwiek z określonych ścieżek dokumentów, całe żądanie kończy się niepowodzeniem.

Więcej informacji o usłudze Cloud Firestore Security Rules znajdziesz w artykule Pierwsze kroki w usłudze Cloud Firestore Security Rules.

Instalowanie emulatora

Aby zainstalować emulator Cloud Firestore, użyj interfejsu wiersza poleceń Firebase i uruchom to polecenie:

firebase setup:emulators:firestore

Uruchamianie emulatora

Zacznij od zainicjowania projektu Firebase w katalogu roboczym. Jest to typowy pierwszy krok podczas korzystania z wiersza poleceń Firebase.

firebase init

Uruchom emulator za pomocą tego polecenia. Emulator będzie działać, dopóki nie zakończysz procesu:

firebase emulators:start --only firestore

W wielu przypadkach chcesz uruchomić emulator, przeprowadzić zestaw testów, a następnie zamknąć go po zakończeniu testów. Możesz to łatwo zrobić za pomocą polecenia emulators:exec:

firebase emulators:exec --only firestore "./my-test-script.sh"

Po uruchomieniu emulator spróbuje działać na porcie domyślnym (8080). Możesz zmienić port emulatora, modyfikując sekcję "emulators" w pliku firebase.json:

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

Przed uruchomieniem emulatora

Zanim zaczniesz korzystać z emulatora, pamiętaj o tych kwestiach:

  • Emulator początkowo wczyta reguły określone w polu firestore.rules w pliku firebase.json. Oczekuje nazwy lokalnego pliku zawierającego Cloud Firestore Security Rules i stosuje te reguły do wszystkich projektów. Jeśli nie podasz lokalnej ścieżki do pliku lub nie użyjesz metody loadFirestoreRules opisanej poniżej, emulator będzie traktować wszystkie projekty jako mające otwarte reguły.
  • Chociaż większość pakietów SDK Firebase działa bezpośrednio z emulatorami, tylko biblioteka @firebase/rules-unit-testing obsługuje tworzenie wersji próbnych auth w regułach zabezpieczeń, co znacznie ułatwia testy jednostkowe. Dodatkowo biblioteka obsługuje kilka funkcji specyficznych dla emulatora, takich jak czyszczenie wszystkich danych, jak wymieniono poniżej.
  • Emulatory będą też akceptować tokeny uwierzytelniania Firebase w wersji produkcyjnej dostarczane przez pakiety SDK klienta i odpowiednio oceniać reguły, co umożliwia bezpośrednie łączenie aplikacji z emulatorami w testach integracyjnych i ręcznych.

Uruchamianie lokalnych testów jednostkowych

Uruchamianie lokalnych testów jednostkowych za pomocą pakietu SDK w JavaScript w wersji 9

Firebase udostępnia bibliotekę testów jednostkowych reguł zabezpieczeń w pakiecie JavaScript SDK w wersji 9 i w pakiecie SDK w wersji 8. Interfejsy API biblioteki znacznie się od siebie różnią. Zalecamy korzystanie z biblioteki testowej w wersji 9, która jest bardziej usprawniona i wymaga mniej konfiguracji, aby połączyć się z emulatorami, a tym samym bezpiecznie uniknąć przypadkowego użycia zasobów produkcyjnych. Aby zapewnić wsteczną zgodność, nadal udostępniamy bibliotekę testową v8.

Użyj modułu @firebase/rules-unit-testing, aby korzystać z emulatora działającego lokalnie. Jeśli wystąpią przekroczenia limitu czasu lub błędy ECONNREFUSED, sprawdź, czy emulator jest uruchomiony.

Zdecydowanie zalecamy korzystanie z najnowszej wersji Node.js, aby móc używać notacji async/await. Prawie wszystkie działania, które możesz chcieć przetestować, obejmują funkcje asynchroniczne, a moduł testowy jest przeznaczony do pracy z kodem opartym na obietnicach.

Biblioteka testów jednostkowych reguł w wersji 9 zawsze rozpoznaje emulatory i nigdy nie korzysta z zasobów produkcyjnych.

Bibliotekę importujesz za pomocą instrukcji importu modułowego w wersji 9. Przykład:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

Po zaimportowaniu implementacja testów jednostkowych obejmuje:

  • Tworzenie i konfigurowanie RulesTestEnvironment z wywołaniem funkcji initializeTestEnvironment.
  • Konfigurowanie danych testowych bez uruchamiania reguł za pomocą wygodnej metody, która umożliwia tymczasowe ich pominięcieRulesTestEnvironment.withSecurityRulesDisabled.
  • Konfigurowanie pakietu testów i funkcji wywoływanych przed i po każdym teście z wywołaniami funkcji czyszczących dane testowe i środowisko, np. RulesTestEnvironment.cleanup() lub RulesTestEnvironment.clearFirestore().
  • Wdrażanie przypadków testowych, które naśladują stany uwierzytelniania za pomocą funkcji RulesTestEnvironment.authenticatedContext i RulesTestEnvironment.unauthenticatedContext.

Typowe metody i funkcje narzędziowe

Zobacz też metody testowania specyficzne dla emulatora w pakiecie SDK w wersji 9.

initializeTestEnvironment() => RulesTestEnvironment

Ta funkcja inicjuje środowisko testowe do testowania jednostkowego reguł. Najpierw wywołaj tę funkcję, aby skonfigurować test. Aby wykonanie się powiodło, emulatory muszą być uruchomione.

Funkcja akceptuje opcjonalny obiekt definiujący TestEnvironmentConfig, który może składać się z identyfikatora projektu i ustawień konfiguracji emulatora.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

Ta metoda tworzy obiekt RulesTestContext, który działa jak uwierzytelniony użytkownik uwierzytelniania. Żądania utworzone za pomocą zwróconego kontekstu będą miały dołączony token uwierzytelniania. Opcjonalnie możesz przekazać obiekt definiujący niestandardowe roszczenia lub zastąpienia ładunków tokena uwierzytelniania.

Użyj zwróconego obiektu kontekstu testowego w testach, aby uzyskać dostęp do skonfigurowanych instancji emulatora, w tym tych skonfigurowanych za pomocą initializeTestEnvironment.

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", {  });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

Ta metoda tworzy RulesTestContext, który działa jak klient niezalogowany za pomocą uwierzytelniania. Żądania utworzone za pomocą zwróconego kontekstu nie będą miały dołączonych tokenów Uwierzytelniania Firebase.

Użyj zwróconego obiektu kontekstu testowego w testach, aby uzyskać dostęp do skonfigurowanych instancji emulatora, w tym tych skonfigurowanych za pomocą initializeTestEnvironment.

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

Uruchom funkcję konfiguracji testu z kontekstem, który zachowuje się tak, jakby reguły zabezpieczeń były wyłączone.

Ta metoda przyjmuje funkcję wywołania zwrotnego, która przyjmuje kontekst Security-Rules-bypassing i zwraca obietnicę. Kontekst zostanie zniszczony po rozwiązaniu lub odrzuceniu obietnicy.

RulesTestEnvironment.cleanup()

Ta metoda usuwa wszystkie obiekty RulesTestContexts utworzone w środowisku testowym i czyści zasoby bazowe, co umożliwia prawidłowe zakończenie działania.

Ta metoda w żaden sposób nie zmienia stanu emulatorów. Aby zresetować dane między testami, użyj metody czyszczenia danych specyficznej dla emulatora aplikacji.

assertSucceeds(pr: Promise<any>)) => Promise<any>

Jest to funkcja narzędziowa testu.

Funkcja sprawdza, czy podany obiekt Promise opakowujący operację emulatora zostanie rozwiązany bez naruszenia reguł bezpieczeństwa.

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

Jest to funkcja narzędziowa testu.

Funkcja sprawdza, czy podany obiekt Promise opakowujący operację emulatora zostanie odrzucony z powodu naruszenia reguł zabezpieczeń.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

Metody specyficzne dla emulatora

Zobacz też częste metody testowania i funkcje narzędziowe w pakiecie SDK w wersji 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Ta metoda usuwa dane z bazy danych Firestore należące do projektuprojectId skonfigurowanego na potrzeby emulatora Firestore.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

Ta metoda pobiera instancję Firestore dla tego kontekstu testowego. Zwrócona instancja pakietu Firebase JS Client SDK może być używana z interfejsami API pakietu SDK klienta (modułowego w wersji 9 lub zgodnego z wersją 9).

Wizualizacja ocen reguł

Emulator Cloud Firestore umożliwia wizualizację żądań klientów w interfejsie Pakietu emulatorów, w tym śledzenie oceny reguł zabezpieczeń Firebase.

Otwórz kartę Firestore > Żądania, aby wyświetlić szczegółową sekwencję oceny każdego żądania.

Monitor żądań emulatora Firestore pokazujący oceny reguł zabezpieczeń

Generowanie raportów z testów

Po przeprowadzeniu zestawu testów możesz uzyskać dostęp do raportów o pokryciu testami, które pokazują, jak oceniana była każda z Twoich reguł zabezpieczeń.

Aby uzyskać raporty, wyślij zapytanie do udostępnionego punktu końcowego na emulatorze podczas jego działania. Wersja do przeglądarki jest dostępna pod tym adresem URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

Reguły zostaną podzielone na wyrażenia i podwyrażenia, nad którymi możesz najechać kursorem, aby uzyskać więcej informacji, w tym liczbę ocen i zwróconych wartości. Aby uzyskać wersję tych danych w formacie JSON, w zapytaniu podaj ten adres URL:

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

Różnice między emulatorem a wersją produkcyjną

  1. Nie musisz jawnie tworzyć projektu Cloud Firestore. Emulator automatycznie tworzy każdą instancję, do której uzyskuje dostęp.
  2. Emulator Cloud Firestore nie działa z normalnym przepływem Firebase Authentication. Zamiast tego w pakiecie Firebase Test SDK udostępniliśmy metodę initializeTestApp() w bibliotece rules-unit-testing, która przyjmuje pole auth. Utworzony za pomocą tej metody uchwyt Firebase będzie się zachowywać tak, jakby został uwierzytelniony jako podmiot, który podasz. Jeśli przekażesz wartość null, będzie to traktowane jako użytkownik nieuwierzytelniony (np. reguły auth != null nie będą działać).

Rozwiązywanie znanych problemów

Podczas korzystania z emulatora Cloud Firestore możesz napotkać te znane problemy. Aby rozwiązać problemy z nieprawidłowym działaniem, postępuj zgodnie z poniższymi wskazówkami. Te uwagi zostały napisane z myślą o bibliotece testów jednostkowych reguł bezpieczeństwa, ale ogólne podejścia mają zastosowanie do dowolnego pakietu SDK Firebase.

Zachowanie testu jest niespójne

Jeśli testy czasami przechodzą, a czasami nie, nawet bez wprowadzania w nich żadnych zmian, może być konieczne sprawdzenie, czy są one prawidłowo uporządkowane. Większość interakcji z emulatorem ma charakter asynchroniczny, więc sprawdź, czy cały kod asynchroniczny jest prawidłowo uporządkowany. Kolejność możesz ustalić, łącząc obietnice lub używając notacji await.

Sprawdź w szczególności te operacje asynchroniczne:

  • Ustawianie reguł zabezpieczeń, np. initializeTestEnvironment.
  • Odczytywanie i zapisywanie danych, np. za pomocą db.collection("users").doc("alice").get().
  • Oświadczenia operacyjne, w tym assertSucceedsassertFails.

Testy są zaliczane tylko przy pierwszym wczytaniu emulatora.

Emulator jest stanowy. Przechowuje wszystkie zapisane w niej dane w pamięci, więc wszystkie dane są tracone po wyłączeniu emulatora. Jeśli uruchamiasz kilka testów z tym samym identyfikatorem projektu, każdy test może generować dane, które mogą wpływać na kolejne testy. Aby obejść to zachowanie, możesz użyć jednej z tych metod:

  • Używaj unikalnych identyfikatorów projektów w przypadku każdego testu. Pamiętaj, że jeśli zdecydujesz się na to rozwiązanie, w ramach każdego testu musisz wywołać initializeTestEnvironment. Reguły są automatycznie wczytywane tylko w przypadku domyślnego identyfikatora projektu.
  • Zmień strukturę testów, aby nie wchodziły w interakcje z wcześniej zapisanymi danymi (np. używaj innej kolekcji w przypadku każdego testu).
  • Usuń wszystkie dane zapisane podczas testu.

Konfiguracja testu jest bardzo skomplikowana

Podczas konfigurowania testu możesz chcieć zmodyfikować dane w sposób, który nie jest dozwolony w przypadku Cloud Firestore Security Rules. Jeśli reguły komplikują konfigurację testu, użyj w niej RulesTestEnvironment.withSecurityRulesDisabled, aby odczyty i zapisy nie powodowały błędów PERMISSION_DENIED.

Następnie test może wykonywać operacje jako uwierzytelniony lub nieuwierzytelniony użytkownik, korzystając odpowiednio z RulesTestEnvironment.authenticatedContextunauthenticatedContext. Dzięki temu możesz sprawdzić, czy funkcja Cloud Firestore Security Rules prawidłowo zezwala na różne przypadki lub im zapobiega.