1. Bevor Sie beginnen
Cloud Firestore, Cloud Storage für Firebase und die Echtzeitdatenbank basieren auf Konfigurationsdateien, die Sie schreiben, um Lese- und Schreibzugriff zu gewähren. Diese als Sicherheitsregeln bezeichnete Konfiguration kann auch als eine Art Schema für Ihre App dienen. Dies ist einer der wichtigsten Teile der Entwicklung Ihrer Anwendung. Und dieses Codelab führt Sie dabei durch.
Voraussetzungen
- Ein einfacher Editor wie Visual Studio Code, Atom oder Sublime Text
- Node.js 8.6.0 oder höher (um Node.js zu installieren, verwenden Sie nvm ; um Ihre Version zu überprüfen, führen Sie
node --version
aus) - Java 7 oder höher (um Java zu installieren, verwenden Sie diese Anweisungen ; um Ihre Version zu überprüfen, führen Sie
java -version
aus)
Was du tun wirst
In diesem Codelab sichern Sie eine einfache Blog-Plattform, die auf Firestore basiert. Sie verwenden den Firestore-Emulator, um Komponententests anhand der Sicherheitsregeln durchzuführen und sicherzustellen, dass die Regeln den erwarteten Zugriff zulassen und nicht zulassen.
Sie erfahren, wie Sie:
- Gewähren Sie detaillierte Berechtigungen
- Erzwingen Sie Daten- und Typvalidierungen
- Implementieren Sie eine attributbasierte Zugriffskontrolle
- Gewähren Sie Zugriff basierend auf der Authentifizierungsmethode
- Erstellen Sie benutzerdefinierte Funktionen
- Erstellen Sie zeitbasierte Sicherheitsregeln
- Implementieren Sie eine Verweigerungsliste und vorläufige Löschvorgänge
- Verstehen Sie, wann Daten denormalisiert werden müssen, um mehreren Zugriffsmustern gerecht zu werden
2. Einrichten
Dies ist eine Blogging-Anwendung. Hier ist eine allgemeine Zusammenfassung der Anwendungsfunktionalität:
Entwürfe für Blogbeiträge:
- Benutzer können Entwürfe für Blogbeiträge erstellen, die in der
drafts
gespeichert sind. - Der Autor kann einen Entwurf so lange aktualisieren, bis er zur Veröffentlichung bereit ist.
- Wenn es zur Veröffentlichung bereit ist, wird eine Firebase-Funktion ausgelöst, die ein neues Dokument in der
published
Sammlung erstellt. - Entwürfe können vom Autor oder von Site-Moderatoren gelöscht werden
Veröffentlichte Blogbeiträge:
- Veröffentlichte Beiträge können nicht von Benutzern erstellt werden, sondern nur über eine Funktion.
- Sie können nur vorläufig gelöscht werden, wodurch ein
visible
Attribut auf „false“ aktualisiert wird.
Kommentare
- Veröffentlichte Beiträge erlauben Kommentare, die eine Untersammlung zu jedem veröffentlichten Beitrag darstellen.
- Um Missbrauch vorzubeugen, müssen Benutzer über eine verifizierte E-Mail-Adresse verfügen und dürfen sich nicht in einer Sperrliste befinden, um einen Kommentar hinterlassen zu können.
- Kommentare können nur innerhalb einer Stunde nach der Veröffentlichung aktualisiert werden.
- Kommentare können vom Kommentarautor, dem Autor des ursprünglichen Beitrags oder von Moderatoren gelöscht werden.
Zusätzlich zu den Zugriffsregeln erstellen Sie Sicherheitsregeln, die erforderliche Felder und Datenvalidierungen erzwingen.
Alles geschieht lokal mit der Firebase Emulator Suite.
Holen Sie sich den Quellcode
In diesem Codelab beginnen Sie mit Tests für die Sicherheitsregeln, jedoch mit minimalen Sicherheitsregeln selbst. Sie müssen also zunächst die Quelle klonen, um die Tests auszuführen:
$ git clone https://github.com/FirebaseExtended/codelab-rules.git
Wechseln Sie dann in das Verzeichnis „initial-state“, wo Sie für den Rest dieses Codelabs arbeiten werden:
$ cd codelab-rules/initial-state
Installieren Sie nun die Abhängigkeiten, damit Sie die Tests ausführen können. Wenn Sie eine langsamere Internetverbindung haben, kann dies ein oder zwei Minuten dauern:
# Move into the functions directory, install dependencies, jump out. $ cd functions && npm install && cd -
Holen Sie sich die Firebase-CLI
Die Emulator Suite, die Sie zum Ausführen der Tests verwenden, ist Teil der Firebase-CLI (Befehlszeilenschnittstelle), die mit dem folgenden Befehl auf Ihrem Computer installiert werden kann:
$ npm install -g firebase-tools
Bestätigen Sie als Nächstes, dass Sie über die neueste Version der CLI verfügen. Dieses Codelab sollte mit Version 8.4.0 oder höher funktionieren, spätere Versionen enthalten jedoch weitere Fehlerkorrekturen.
$ firebase --version 9.10.2
3. Führen Sie die Tests durch
In diesem Abschnitt führen Sie die Tests lokal aus. Dies bedeutet, dass es Zeit ist, die Emulator Suite zu starten.
Starten Sie die Emulatoren
Die Anwendung, mit der Sie arbeiten, verfügt über drei Hauptsammlungen von Firestore: drafts
enthalten Blogbeiträge, die in Bearbeitung sind, die published
Sammlung enthält die Blogbeiträge, die veröffentlicht wurden, und comments
sind eine Untersammlung zu veröffentlichten Beiträgen. Das Repo enthält Komponententests für die Sicherheitsregeln, die die Benutzerattribute und andere Bedingungen definieren, die ein Benutzer zum Erstellen, Lesen, Aktualisieren und Löschen von Dokumenten in drafts
, published
Sammlungen und comments
benötigt. Sie schreiben die Sicherheitsregeln, damit diese Tests bestehen.
Zunächst ist Ihre Datenbank gesperrt: Lese- und Schreibvorgänge in der Datenbank werden allgemein verweigert und alle Tests schlagen fehl. Während Sie Sicherheitsregeln schreiben, werden die Tests bestanden. Um die Tests anzuzeigen, öffnen Sie functions/test.js
in Ihrem Editor.
Starten Sie in der Befehlszeile die Emulatoren mit emulators:exec
und führen Sie die Tests aus:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"
Scrollen Sie zum Anfang der Ausgabe:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test" i emulators: Starting emulators: functions, firestore, hosting ⚠ functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: auth, database, pubsub ⚠ functions: Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect. i firestore: Importing data from /Users/user/src/firebase/rules-codelab/initial-state/.seed/firestore_export/firestore_export.overall_export_metadata i firestore: Firestore Emulator logging to firestore-debug.log ⚠ hosting: Authentication error when trying to fetch your current web app configuration, have you run firebase login? ⚠ hosting: Could not fetch web app configuration and there is no cached configuration on this machine. Check your internet connection and make sure you are authenticated. To continue, you must call firebase.initializeApp({...}) in your code before using Firebase. i hosting: Serving hosting files from: public ✔ hosting: Local server: http://localhost:5000 i functions: Watching "/Users/user/src/firebase/rules-codelab/initial-state/functions" for Cloud Functions... ✔ functions[publishPost]: http function initialized (http://localhost:5001/codelab/us-central1/publishPost). ✔ functions[softDelete]: http function initialized (http://localhost:5001/codelab/us-central1/softDelete). i Running script: pushd functions; npm test ~/src/firebase/rules-codelab/initial-state/functions ~/src/firebase/rules-codelab/initial-state > functions@ test /Users/user/src/firebase/rules-codelab/initial-state/functions > mocha (node:76619) ExperimentalWarning: Conditional exports is an experimental feature. This feature could change at any time Draft blog posts 1) can be created with required fields by the author 2) can be updated by author if immutable fields are unchanged 3) can be read by the author and moderator Published blog posts 4) can be read by everyone; created or deleted by no one 5) can be updated by author or moderator Comments on published blog posts 6) can be read by anyone with a permanent account 7) can be created if email is verfied and not blocked 8) can be updated by author for 1 hour after creation 9) can be deleted by an author or moderator 0 passing (848ms) 9 failing ...
Im Moment gibt es 9 Ausfälle. Während Sie die Regeldatei erstellen, können Sie den Fortschritt messen, indem Sie beobachten, wie weitere Tests durchgeführt werden.
4. Erstellen Sie Blog-Post-Entwürfe.
Da sich der Zugriff auf Entwürfe von Blogbeiträgen stark vom Zugriff auf veröffentlichte Blogbeiträge unterscheidet, speichert diese Blogging-App Entwürfe von Blogbeiträgen in einer separaten Sammlung, /drafts
. Auf Entwürfe kann nur der Autor oder ein Moderator zugreifen und verfügt über Validierungen für erforderliche und unveränderliche Felder.
Wenn Sie die Datei firestore.rules
öffnen, finden Sie eine Standardregeldatei:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Die Match-Anweisung match /{document=**}
verwendet die **
Syntax, um rekursiv auf alle Dokumente in Untersammlungen anzuwenden. Und da es sich um die oberste Ebene handelt, gilt derzeit für alle Anfragen die gleiche pauschale Regel, unabhängig davon, wer die Anfrage stellt oder welche Daten sie lesen oder schreiben möchte.
Entfernen Sie zunächst die innerste match-Anweisung und ersetzen Sie sie durch match /drafts/{draftID}
. (Kommentare zur Struktur von Dokumenten können in Regeln hilfreich sein und werden in dieses Codelab aufgenommen; sie sind immer optional.)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
}
}
}
Die erste Regel, die Sie für Entwürfe schreiben, steuert, wer die Dokumente erstellen kann. In diesem Antrag können Entwürfe nur von der als Autor aufgeführten Person erstellt werden. Überprüfen Sie, ob die UID der Person, die den Antrag gestellt hat, mit der im Dokument aufgeführten UID übereinstimmt.
Die erste Bedingung für die Erstellung ist:
request.resource.data.authorUID == request.auth.uid
Als Nächstes können Dokumente nur erstellt werden, wenn sie die drei erforderlichen Felder authorUID
, createdAt
“ und „ title
enthalten. (Der Benutzer gibt das Feld createdAt
nicht an; dies erzwingt, dass die App es hinzufügen muss, bevor sie versucht, ein Dokument zu erstellen.) Da Sie nur überprüfen müssen, ob die Attribute erstellt werden, können Sie überprüfen, ob request.resource
über alle verfügt diese Schlüssel:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
Die letzte Voraussetzung für die Erstellung eines Blogbeitrags ist, dass der Titel nicht mehr als 50 Zeichen lang sein darf:
request.resource.data.title.size() < 50
Da alle diese Bedingungen wahr sein müssen, verketten Sie sie mit dem logischen UND-Operator &&
. Die erste Regel lautet:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
}
}
}
Führen Sie im Terminal die Tests erneut aus und bestätigen Sie, dass der erste Test erfolgreich ist.
5. Aktualisieren Sie die Entwürfe von Blogbeiträgen.
Als Nächstes bearbeiten Autoren die Entwurfsdokumente, während sie ihre Entwürfe für Blogbeiträge verfeinern. Erstellen Sie eine Regel für die Bedingungen, unter denen ein Beitrag aktualisiert werden kann. Erstens kann nur der Autor seine Entwürfe aktualisieren. Beachten Sie, dass Sie hier die bereits geschriebene UID resource.data.authorUID
überprüfen:
resource.data.authorUID == request.auth.uid
Die zweite Voraussetzung für ein Update besteht darin, dass sich zwei Attribute, authorUID
“ und createdAt
, nicht ändern dürfen:
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]);
Und schließlich sollte der Titel maximal 50 Zeichen lang sein:
request.resource.data.title.size() < 50;
Da diese Bedingungen alle erfüllt sein müssen, verketten Sie sie mit &&
:
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
Die vollständigen Regeln lauten:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
}
}
}
Führen Sie Ihre Tests erneut durch und bestätigen Sie, dass ein weiterer Test erfolgreich ist.
6. Entwürfe löschen und lesen: Attributbasierte Zugriffskontrolle
Ebenso wie Autoren Entwürfe erstellen und aktualisieren können, können sie auch Entwürfe löschen.
resource.data.authorUID == request.auth.uid
Darüber hinaus dürfen Autoren mit einem isModerator
Attribut in ihrem Authentifizierungstoken Entwürfe löschen:
request.auth.token.isModerator == true
Da jede dieser Bedingungen für einen Löschvorgang ausreicht, verketten Sie sie mit einem logischen ODER-Operator ||
:
allow delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Für Lesevorgänge gelten die gleichen Bedingungen, sodass der Regel eine Berechtigung hinzugefügt werden kann:
allow read, delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Die vollständigen Regeln lauten jetzt:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow read, delete: if
// User is draft author
resource.data.authorUID == request.auth.uid ||
// User is a moderator
request.auth.token.isModerator == true;
}
}
}
Führen Sie Ihre Tests erneut durch und bestätigen Sie, dass ein weiterer Test nun erfolgreich ist.
7. Liest, erstellt und löscht veröffentlichte Beiträge: Denormalisierung für unterschiedliche Zugriffsmuster
Da die Zugriffsmuster für veröffentlichte Beiträge und Entwurfsbeiträge so unterschiedlich sind, denormalisiert diese App die Beiträge in separate draft
und published
Sammlungen. Veröffentlichte Beiträge können beispielsweise von jedem gelesen, aber nicht endgültig gelöscht werden, während Entwürfe gelöscht werden können, aber nur vom Autor und den Moderatoren gelesen werden können. Wenn ein Benutzer in dieser App einen Entwurf eines Blogbeitrags veröffentlichen möchte, wird eine Funktion ausgelöst, die den neuen veröffentlichten Beitrag erstellt.
Als Nächstes schreiben Sie die Regeln für veröffentlichte Beiträge. Die einfachsten Regeln beim Schreiben sind, dass veröffentlichte Beiträge von jedem gelesen und von niemandem erstellt oder gelöscht werden können. Fügen Sie diese Regeln hinzu:
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
}
Wenn Sie diese zu den vorhandenen Regeln hinzufügen, wird die gesamte Regeldatei zu:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow read, delete: if
// User is draft author
resource.data.authorUID == request.auth.uid ||
// User is a moderator
request.auth.token.isModerator == true;
}
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
}
}
}
Führen Sie die Tests erneut aus und bestätigen Sie, dass ein weiterer Test erfolgreich ist.
8. Aktualisieren veröffentlichter Beiträge: Benutzerdefinierte Funktionen und lokale Variablen
Die Bedingungen für die Aktualisierung eines veröffentlichten Beitrags sind:
- Dies kann nur vom Autor oder Moderator durchgeführt werden
- Es muss alle erforderlichen Felder enthalten.
Da Sie bereits Bedingungen für die Tätigkeit als Autor oder Moderator geschrieben haben, könnten Sie die Bedingungen kopieren und einfügen, aber mit der Zeit könnte es schwierig werden, sie zu lesen und beizubehalten. Stattdessen erstellen Sie eine benutzerdefinierte Funktion, die die Logik für die Rolle als Autor oder Moderator kapselt. Anschließend rufen Sie es unter mehreren Bedingungen auf.
Erstellen Sie eine benutzerdefinierte Funktion
Erstellen Sie über der Match-Anweisung für Entwürfe eine neue Funktion namens isAuthorOrModerator
, die als Argumente ein Post-Dokument (dies funktioniert sowohl für Entwürfe als auch für veröffentlichte Beiträge) und das Authentifizierungsobjekt des Benutzers verwendet:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
}
match /drafts/{postID} {
allow create: ...
allow update: ...
...
}
match /published/{postID} {
allow read: ...
allow create, delete: ...
}
}
}
Verwenden Sie lokale Variablen
Verwenden Sie innerhalb der Funktion das Schlüsselwort let
, um die Variablen isAuthor
und isModerator
festzulegen. Alle Funktionen müssen mit einer Return-Anweisung enden und unsere gibt einen booleschen Wert zurück, der angibt, ob eine der Variablen wahr ist:
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
Rufen Sie die Funktion auf
Jetzt aktualisieren Sie die Regel für Entwürfe, um diese Funktion aufzurufen, und achten dabei darauf, resource.data
als erstes Argument zu übergeben:
// Draft blog posts
match /drafts/{draftID} {
...
// Can be deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
Jetzt können Sie eine Bedingung zum Aktualisieren veröffentlichter Beiträge schreiben, die auch die neue Funktion verwendet:
allow update: if isAuthorOrModerator(resource.data, request.auth);
Validierungen hinzufügen
Einige der Felder eines veröffentlichten Beitrags sollten nicht geändert werden, insbesondere die Felder url
, authorUID
und publishedAt
sind unveränderlich. Die anderen beiden Felder title
und content
sowie visible
müssen nach einem Update weiterhin vorhanden sein. Fügen Sie Bedingungen hinzu, um diese Anforderungen für Aktualisierungen veröffentlichter Beiträge durchzusetzen:
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
])
Erstellen Sie selbst eine benutzerdefinierte Funktion
Und schließlich fügen Sie eine Bedingung hinzu, dass der Titel weniger als 50 Zeichen lang sein muss. Da es sich hierbei um wiederverwendete Logik handelt, können Sie dies erreichen, indem Sie eine neue Funktion namens titleIsUnder50Chars
erstellen. Mit der neuen Funktion lautet die Bedingung für die Aktualisierung eines veröffentlichten Beitrags:
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
Und die vollständige Regeldatei lautet:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
function titleIsUnder50Chars(post) {
return post.title.size() < 50;
}
// Draft blog posts
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
titleIsUnder50Chars(request.resource.data);
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
titleIsUnder50Chars(request.resource.data);
// Can be read or deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
// Published blog posts are denormalized from drafts
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
}
}
}
Führen Sie die Tests erneut aus. Zu diesem Zeitpunkt sollten Sie 5 Tests bestanden und 4 Tests nicht bestanden haben.
9. Kommentare: Untersammlungen und Anmeldeanbieterberechtigungen
Die veröffentlichten Beiträge erlauben Kommentare und die Kommentare werden in einer Untersammlung des veröffentlichten Beitrags gespeichert ( /published/{postID}/comments/{commentID}
). Standardmäßig gelten die Regeln einer Sammlung nicht für Untersammlungen. Sie möchten nicht, dass für die Kommentare dieselben Regeln gelten, die für das übergeordnete Dokument des veröffentlichten Beitrags gelten. Du wirst verschiedene herstellen.
Um Regeln für den Zugriff auf die Kommentare zu schreiben, beginnen Sie mit der match-Anweisung:
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `comment`: string, < 500 characters, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
Kommentare lesen: Kann nicht anonym sein
Bei dieser App können nur Benutzer die Kommentare lesen, die ein dauerhaftes Konto erstellt haben, kein anonymes Konto. Um diese Regel durchzusetzen, suchen Sie nach dem Attribut sign_in_provider
, das sich in jedem auth.token
Objekt befindet:
allow read: if request.auth.token.firebase.sign_in_provider != "anonymous";
Führen Sie Ihre Tests erneut durch und bestätigen Sie, dass ein weiterer Test erfolgreich ist.
Kommentare erstellen: Überprüfung einer Deny-Liste
Es gibt drei Bedingungen zum Erstellen eines Kommentars:
- Ein Benutzer muss über eine bestätigte E-Mail-Adresse verfügen
- Der Kommentar muss weniger als 500 Zeichen lang sein
- Sie können nicht auf einer Liste gesperrter Benutzer stehen, die im Firestore in der Sammlung
bannedUsers
gespeichert ist. Nehmen Sie diese Bedingungen einzeln:
request.auth.token.email_verified == true
request.resource.data.comment.size() < 500
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
Die letzte Regel zum Erstellen von Kommentaren lautet:
allow create: if
// User has verified email
(request.auth.token.email_verified == true) &&
// UID is not on bannedUsers list
!(exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
Die gesamte Regeldatei lautet jetzt:
For bottom of step 9
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
function titleIsUnder50Chars(post) {
return post.title.size() < 50;
}
// Draft blog posts
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User is author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and createdAt fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
titleIsUnder50Chars(request.resource.data);
allow update: if
// User is author
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
titleIsUnder50Chars(request.resource.data);
// Can be read or deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
// Published blog posts are denormalized from drafts
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
}
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
// `comment`: string, < 500 characters, required
// Must have permanent account to read comments
allow read: if !(request.auth.token.firebase.sign_in_provider == "anonymous");
allow create: if
// User has verified email
request.auth.token.email_verified == true &&
// Comment is under 500 characters
request.resource.data.comment.size() < 500 &&
// UID is not on the block list
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
}
}
}
Führen Sie die Tests erneut aus und stellen Sie sicher, dass ein weiterer Test erfolgreich ist.
10. Kommentare aktualisieren: Zeitbasierte Regeln
Die Geschäftslogik für Kommentare besteht darin, dass sie nach der Erstellung eine Stunde lang vom Kommentarautor bearbeitet werden können. Um dies zu implementieren, verwenden Sie den Zeitstempel createdAt
.
Um zunächst festzustellen, dass der Benutzer der Autor ist:
request.auth.uid == resource.data.authorUID
Als nächstes, dass der Kommentar innerhalb der letzten Stunde erstellt wurde:
(request.time - resource.data.createdAt) < duration.value(1, 'h');
Kombiniert man diese mit dem logischen UND-Operator, ergibt sich folgende Regel zum Aktualisieren von Kommentaren:
allow update: if
// is author
request.auth.uid == resource.data.authorUID &&
// within an hour of comment creation
(request.time - resource.data.createdAt) < duration.value(1, 'h');
Führen Sie die Tests erneut aus und stellen Sie sicher, dass ein weiterer Test erfolgreich ist.
11. Kommentare löschen: Überprüfung auf übergeordnete Eigentümerschaft
Kommentare können vom Kommentarautor, einem Moderator oder dem Autor des Blogbeitrags gelöscht werden.
Erstens: Da die Hilfsfunktion, die Sie zuvor hinzugefügt haben, nach einem Feld authorUID
sucht, das entweder in einem Beitrag oder einem Kommentar vorhanden sein könnte, können Sie die Hilfsfunktion wiederverwenden, um zu überprüfen, ob der Benutzer der Autor oder Moderator ist:
isAuthorOrModerator(resource.data, request.auth)
Um zu überprüfen, ob der Benutzer der Autor des Blog-Beitrags ist, verwenden Sie einen get
Befehl, um den Beitrag im Firestore nachzuschlagen:
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID
Da jede dieser Bedingungen ausreichend ist, verwenden Sie zwischen ihnen einen logischen ODER-Operator:
allow delete: if
// is comment author or moderator
isAuthorOrModerator(resource.data, request.auth) ||
// is blog post author
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID;
Führen Sie die Tests erneut aus und stellen Sie sicher, dass ein weiterer Test erfolgreich ist.
Und die gesamte Regeldatei lautet:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
function titleIsUnder50Chars(post) {
return post.title.size() < 50;
}
// Draft blog posts
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User is author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and createdAt fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
titleIsUnder50Chars(request.resource.data);
allow update: if
// User is author
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
titleIsUnder50Chars(request.resource.data);
// Can be read or deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
// Published blog posts are denormalized from drafts
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
}
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
// `comment`: string, < 500 characters, required
// Must have permanent account to read comments
allow read: if !(request.auth.token.firebase.sign_in_provider == "anonymous");
allow create: if
// User has verified email
request.auth.token.email_verified == true &&
// Comment is under 500 characters
request.resource.data.comment.size() < 500 &&
// UID is not on the block list
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
allow update: if
// is author
request.auth.uid == resource.data.authorUID &&
// within an hour of comment creation
(request.time - resource.data.createdAt) < duration.value(1, 'h');
allow delete: if
// is comment author or moderator
isAuthorOrModerator(resource.data, request.auth) ||
// is blog post author
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID;
}
}
}
12. Nächste Schritte
Glückwunsch! Sie haben die Sicherheitsregeln geschrieben, die alle Tests bestanden und die Anwendung gesichert haben!
Hier sind einige verwandte Themen, in die Sie als Nächstes eintauchen sollten:
- Blogbeitrag : So überprüfen Sie Sicherheitsregeln im Code
- Codelab : Spaziergang durch die lokale Erstentwicklung mit den Emulatoren
- Video : So verwenden Sie das Setup-CI für emulatorbasierte Tests mithilfe von GitHub-Aktionen