Sicherheitsregeln testen

Beim Erstellen Ihrer Anwendung möchten Sie eventuell den Zugriff auf Ihre Firestore-Datenbank sperren. Dazu benötigen Sie aber detailliertere Firestore-Sicherheitsregeln. Neben dem Prototyping und Testen der allgemeinen Funktionen und des Verhaltens Ihrer Anwendung mit dem Firestore-Emulator können Sie damit auch Einheitentests schreiben, die das Verhalten Ihrer Firestore-Sicherheitsregeln prüfen.

Kurzanleitung

Einige grundlegende Testfälle mit einfachen Regeln bieten die Kurzanleitung-Beispiel.

Informationen zu Firestore-Sicherheitsregeln

Implementieren Sie Firebase Authentication und die Firestore-Sicherheitsregeln für eine serverlose Authentifizierung, Autorisierung und Datenvalidierung, wenn Sie die Mobil- und Webclientbibliotheken verwenden.

Die Firestore-Sicherheitsregeln bestehen aus zwei Teilen:

  1. Eine match-Anweisung, mit der Dokumente in Ihrer Datenbank ermittelt werden.
  2. Ein allow-Ausdruck, mit dem der Zugriff auf diese Dokumente gesteuert wird.

Firebase Authentication prüft die Anmeldedaten der Nutzer und bildet die Grundlage für nutzerbasierte und rollenbasierte Zugriffssysteme.

Jede Datenbankanfrage aus einer Mobil-/Webclientbibliothek von Firestore wird anhand Ihrer Sicherheitsregeln ausgewertet, bevor Daten gelesen oder geschrieben werden. Wenn der Zugriff auf einen der angegebenen Dokumentpfade durch die Regeln verweigert wird, schlägt die gesamte Anfrage fehl.

Weitere Informationen zu Firestore-Sicherheitsregeln finden Sie unter Erste Schritte mit Sicherheitsregeln.

Emulator installieren

Zum Installieren des Firestore-Emulators verwenden Sie die Firebase CLI und führen den folgenden Befehl aus:

firebase setup:emulators:firestore

Emulator starten

Beginnen Sie mit der Initialisierung eines Firebase-Projekts in Ihrem Arbeitsverzeichnis. Dies ist ein häufiger erster Schritt bei der Verwendung der Firebase CLI.

firebase init

Starten Sie den Emulator mit dem unten aufgeführten Befehl. Der Emulator wird ausgeführt, bis Sie den Prozess beenden:

firebase emulators:start --only firestore

Sie werden häufig den Emulator starten, eine Testsuite ausführen und dann den Emulator nach Beendigung der Tests herunterfahren. Dies können Sie ganz einfach mit dem Befehl emulators:exec tun:

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

Nach dem Starten versucht der Emulator, auf einem Standardport (8080) zu laufen. Sie können den Port des Emulators ändern, indem Sie den Abschnitt "emulators" in der firebase.json-Datei ändern:

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

Vor dem Starten des Emulators

Für die Verwendung des Emulators ist Folgendes zu beachten:

  • Der Emulator lädt zuerst die Regeln, die im Feld firestore.rules Ihrer Datei firebase.json angegeben sind. Er erwartet den Namen einer lokalen Datei mit Ihren Firestore-Sicherheitsregeln und wendet diese Regeln auf alle Projekte an. Wenn Sie den lokalen Dateipfad nicht angeben oder die Methode loadFirestoreRules wie unten beschrieben anwenden, behandelt der Emulator alle Projekte als solche mit offenen Regeln.
  • Während die meisten Firebase-SDKs direkt mit den Emulatoren arbeiten, unterstützt nur die @firebase/rules-unit-testing-Bibliothek das Mocking von auth in den Sicherheitsregeln. Das vereinfacht die Unittests. Darüber hinaus unterstützt die Bibliothek einige Emulator-spezifische Funktionen wie das Löschen aller Daten, wie unten aufgeführt.
  • Die Emulatoren akzeptieren auch Firebase-Auth-Produktionstoken, die über Client-SDKs bereitgestellt werden, und werten die Regeln entsprechend aus. Dadurch kann Ihre Anwendung bei Integrations- und manuellen Tests direkt mit den Emulatoren verbunden werden.

Lokale Unittests ausführen

Lokale Unittests mit dem v9 JavaScript SDK ausführen

Firebase bietet sowohl mit dem JavaScript-SDK der Version 9 als auch mit dem SDK der Version 8 eine Unittest-Bibliothek für Sicherheitsregeln an. Die Bibliotheks-APIs unterscheiden sich erheblich. Wir empfehlen die v9-Testbibliothek, die optimiert ist und weniger Einrichtungsschritte erfordert, um eine Verbindung zu Emulatoren herzustellen und daher versehentliche Verwendung von Produktionsressourcen zu vermeiden. Aus Gründen der Abwärtskompatibilität stellen wir weiterhin die v8-Testbibliothek zur Verfügung.

Verwenden Sie das Modul @firebase/rules-unit-testing, um den Emulator anzusprechen, der lokal ausgeführt wird. Wenn Zeitüberschreitungen oder ECONNREFUSED-Fehler auftreten, prüfen Sie, ob der Emulator tatsächlich ausgeführt wird.

Es wird dringend empfohlen, eine aktuelle Version von Node.js zu verwenden, damit Sie die async/await-Notation verwenden können. Fast das gesamte Verhalten, das Sie testen möchten, umfasst asynchrone Funktionen. Das Testmodul ist für die Verwendung mit Promise-basiertem Code ausgelegt.

Die v9-Regeln Einheitentest Bibliothek kennt immer die Emulatoren und greift niemals auf Ihre Produktionsressourcen zu.

Sie importieren die Bibliothek mithilfe von modularen v9-Importanweisungen. Beispiel:

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.

Nach dem Import umfasst das Implementieren von Unittest Folgendes:

  • RulesTestEnvironment mit einem Aufruf von initializeTestEnvironment erstellen und konfigurieren
  • Einrichtung von Testdaten ohne Auslösung von Regeln, indem Sie eine praktische Methode verwenden, mit der Sie diese vorübergehend umgehen können: RulesTestEnvironment.withSecurityRulesDisabled.
  • Einrichten von Testsuite und Pro-Test vor/nach Hooks mit Aufrufen zur Bereinigung von Testdaten und -umgebungen, z. B. RulesTestEnvironment.cleanup() oder RulesTestEnvironment.clearFirestore().
  • Testfälle implementieren, die Authentifizierungsstatus mit RulesTestEnvironment.authenticatedContext und RulesTestEnvironment.unauthenticatedContext nachahmen

Gängige Methoden und Dienstfunktionen

Weitere Informationen finden Sie unter Emulator-spezifische Testmethoden im v9 SDK.

initializeTestEnvironment() => RulesTestEnvironment

Diese Funktion initialisiert eine Testumgebung für Regel-Unittest. Rufen Sie diese Funktion zuerst zum Testen der Einrichtung auf. Für eine erfolgreiche Ausführung müssen Emulatoren ausgeführt werden.

Die Funktion akzeptiert ein optionales Objekt, das eine TestEnvironmentConfig definiert, die aus einer Projekt-ID und Einstellungen für die Emulatorkonfiguration bestehen kann.

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

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

Diese Methode erstellt einen RulesTestContext, der sich wie ein authentifizierter Authentifizierungsnutzer verhält. Anfragen, die über den zurückgegebenen Kontext erstellt werden, haben ein simuliertes Authentifizierungstoken. Optional können Sie ein Objekt übergeben, das benutzerdefinierte Anforderungen oder Überschreibungen für Authentifizierungstoken-Nutzlasten definiert.

Verwenden Sie das zurückgegebene Testkontextobjekt in Ihren Tests, um auf alle konfigurierten Emulatorinstanzen zuzugreifen, einschließlich solchen, die mit initializeTestEnvironment konfiguriert wurden.

// 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

Diese Methode erstellt einen RulesTestContext, der sich wie ein Client verhält, der nicht über Authentifizierung angemeldet ist. An Anfragen, die über den zurückgegebenen Kontext erstellt werden, werden keine Firebase Auth-Token angehängt.

Verwenden Sie das zurückgegebene Testkontextobjekt in Ihren Tests, um auf alle konfigurierten Emulatorinstanzen zuzugreifen, einschließlich solchen, die mit initializeTestEnvironment konfiguriert wurden.

// 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()

Führen Sie eine Testeinrichtungsfunktion mit einem Kontext aus, der sich so verhält, als seien die Sicherheitsregeln deaktiviert.

Diese Methode verwendet eine Callback-Funktion, die den Kontext verwendet, der Sicherheitsregeln umgeht, und die ein Promise zurückgibt. Der Kontext wird gelöscht, sobald das Promise aufgelöst oder abgelehnt wird.

RulesTestEnvironment.cleanup()

Diese Methode löscht alle RulesTestContexts, die in der Testumgebung erstellt wurden, und bereinigt die zugrunde liegenden Ressourcen, was einen sauberen Beendigung ermöglicht.

Diese Methode ändert den Status der Emulatoren nicht. Verwenden Sie die Methode für spezifische Daten des Anwendungsemulators, um Daten zwischen Tests zurückzusetzen.

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

Dies ist eine Dienstfunktion für Testläufe.

Die Funktion bestätigt, dass der bereitgestellte Promise, der einen Emulator-Vorgang verpackt, ohne Verstöße gegen Sicherheitsregeln aufgelöst wird.

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

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

Dies ist eine Dienstfunktion für Testläufe.

Die Funktion bestätigt, dass der bereitgestellte Promise, der einen Emulator-Vorgang verpackt, mit einem Verstoß gegen Sicherheitsregeln abgelehnt wird.

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

Emulator-spezifische Methoden

Weitere Informationen finden Sie unter Allgemeine Testmethoden und Dienstfunktionen im v9 SDK.

RulesTestEnvironment.clearFirestore() => Promise<void>

Mit dieser Methode werden Daten in der Firestore-Datenbank gelöscht, die zu der für den Firestore-Emulator konfigurierten projectId gehören.

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

Diese Methode ruft eine Firestore-Instanz für diesen Testkontext ab. Die zurückgegebene Firebase JS Client SDK-Instanz kann mit den Client SDK APIs verwendet werden (v9 modular oder v9 compat).

Evaluierungen von Regeln visualisieren

Mit dem Firestore-Emulator können Sie Clientanfragen in der Benutzeroberfläche der Emulator Suite visualisieren, einschließlich des Bewertungs-Tracings für Firebase-Sicherheitsregeln.

Öffnen Sie den Tab Firestore > Anfragen, um die detaillierte Bewertungssequenz für jede Anfrage aufzurufen.

Firestore-Emulator-Anfragen-Monitor, der Sicherheitsregelbewertungen zeigt

Testberichte erstellen

Nachdem Sie eine Reihe von Tests ausgeführt haben, können Sie auf Berichte zur Testabdeckung zugreifen, die Aufschluss darüber geben, wie die einzelnen Sicherheitsregeln bewertet wurden.

Um die Berichte abzurufen, fragen Sie einen bereitgestellten Endpunkt im Emulator ab, während er ausgeführt wird. Verwenden Sie für eine browserfreundliche Version die folgende URL:

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

Dadurch werden Ihre Regeln in Ausdrücke und Teilausdrücke aufgeteilt. Bewegen Sie die Maus auf einen Ausdruck oder Teilausdruck, um weitere Informationen zu erhalten, einschließlich der Anzahl der Bewertungen und der zurückgegebenen Werte. Fügen Sie für die JSON-Rohversion dieser Daten die folgende URL in Ihre Abfrage ein:

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

Unterschiede zwischen Emulator und Produktion

  1. Sie müssen kein Firestore-Projekt explizit erstellen. Der Emulator legt automatisch jede Instanz an, auf die zugegriffen wird.
  2. Der Firestore-Emulator funktioniert nicht mit dem herkömmlichen Firebase-Authentifizierungsablauf. Stattdessen wurde im Firebase Test SDK die Methode initializeTestApp() in der Bibliothek rules-unit-testing bereitgestellt, was ein auth-Feld einnimmt. Das mit dieser Methode erstellte Firebase-Handle verhält sich so, als hätte es sich erfolgreich authentifiziert, unabhängig von der von Ihnen bereitgestellten Entität. Wenn Sie null übergeben, verhält es sich wie ein nicht authentifizierter Nutzer (auth != null-Regeln schlagen zum Beispiel fehl).

Bekannte Probleme beheben

Bei Verwendung des Firestore-Emulators treten möglicherweise die unten aufgeführten bekannten Probleme auf. Folgen Sie der Anleitung unten, um unerwartetes Verhalten zu korrigieren. Diese Hinweise wurden für die Bibliothek für Einheitentests für Sicherheitsregeln geschrieben. Die allgemeinen Ansätze gelten jedoch für jedes Firebase SDK.

Das Testverhalten ist inkonsistent

Wenn Ihre Tests hin und wieder erfolgreich sind und fehlschlagen, obwohl Sie die Tests selbst nicht geändert haben, müssen Sie gegebenenfalls überprüfen, ob sie in der richtigen Reihenfolge ablaufen. Die meisten Interaktionen mit dem Emulator sind asynchron. Überprüfen Sie daher, ob der gesamte asynchrone Code ordnungsgemäß sequenziert ist. Sie können die Reihenfolge korrigieren, indem Sie Versprechen verketten oder die await-Notation häufiger verwenden.

Überprüfen Sie insbesondere die folgenden asynchronen Vorgänge:

  • Sicherheitsregeln festlegen, z. B. mit initializeTestEnvironment.
  • Lesen und Schreiben von Daten, z. B. mit db.collection("users").doc("alice").get().
  • Operative Assertionen, einschließlich assertSucceeds und assertFails.

Tests sind nur dann erfolgreich, wenn Sie den Emulator das erste Mal laden

Der Emulator ist zustandsorientiert. Alle in den Speicher geschriebenen Daten werden gespeichert, sodass die Daten bei jedem Herunterfahren des Emulators verloren gehen. Wenn Sie mehrere Tests mit derselben Projekt-ID durchführen, kann jeder Test Daten liefern, die nachfolgende Tests beeinflussen können. Sie können dieses Verhalten mit einer der folgenden Methoden umgehen:

  • Verwenden Sie für jeden Test eindeutige Projekt-IDs. Wenn Sie sich für dieses Vorgehen entscheiden, muss initializeTestEnvironment bei jedem Test aufgerufen werden. Regeln werden automatisch nur für die Standardprojekt-ID geladen.
  • Strukturieren Sie Ihre Tests so um, dass sie nicht mit zuvor geschriebenen Daten interagieren. Verwenden Sie beispielsweise für jeden Test eine andere Sammlung.
  • Löschen Sie alle während eines Tests geschriebenen Daten.

Die Testeinrichtung ist sehr kompliziert

Beim Einrichten Ihres Tests möchten Sie vielleicht die Daten so ändern, wie Ihre Firestore-Sicherheitsregeln letztendlich nicht zulassen. Wenn die Testeinrichtung durch Ihre Regeln sehr komplex wird, versuchen Sie, in den Einrichtungsschritten RulesTestEnvironment.withSecurityRulesDisabled zu verwenden. So werden Lese- und Schreibvorgänge keine PERMISSION_DENIED-Fehler auslösen.

Danach kann Ihr Test Vorgänge als authentifizierter oder nicht authentifizierten Nutzer mithilfe von RulesTestEnvironment.authenticatedContext bzw. unauthenticatedContext ausführen. Auf diese Weise können Sie prüfen, ob Ihre Firestore-Sicherheitsregeln verschiedene Fälle ordnungsgemäß zulassen / ablehnen.