1. Sebelum Anda mulai
Cloud Firestore, Cloud Storage for Firebase, dan Realtime Database bergantung pada file konfigurasi yang Anda tulis untuk memberikan akses baca dan tulis. Konfigurasi tersebut, yang disebut Aturan Keamanan, juga dapat berfungsi sebagai semacam skema untuk aplikasi Anda. Ini adalah salah satu bagian terpenting dalam mengembangkan aplikasi Anda. Dan codelab ini akan memandu Anda.
Prasyarat
- Editor sederhana seperti Visual Studio Code, Atom, atau Sublime Text
- Node.js 8.6.0 atau lebih tinggi (untuk menginstal Node.js, gunakan nvm ; untuk memeriksa versi Anda, jalankan
node --version
) - Java 7 atau lebih tinggi (untuk menginstal Java gunakan instruksi ini ; untuk memeriksa versi Anda, jalankan
java -version
)
Apa yang akan Anda lakukan
Dalam codelab ini, Anda akan mengamankan platform blog sederhana yang dibuat di Firestore. Anda akan menggunakan emulator Firestore untuk menjalankan pengujian unit terhadap Aturan Keamanan, dan memastikan bahwa aturan mengizinkan dan melarang akses yang Anda harapkan.
Anda akan mempelajari cara:
- Berikan izin granular
- Menerapkan validasi data dan tipe
- Menerapkan Kontrol Akses Berbasis Atribut
- Berikan akses berdasarkan metode otentikasi
- Buat fungsi kustom
- Buat Aturan Keamanan berbasis waktu
- Menerapkan daftar penolakan dan penghapusan lunak
- Pahami kapan harus mendenormalisasi data agar memenuhi beberapa pola akses
2. Siapkan
Ini adalah aplikasi blog. Berikut ringkasan tingkat tinggi dari fungsionalitas aplikasi:
Konsep posting blog:
- Pengguna dapat membuat draf postingan blog, yang ada di koleksi
drafts
. - Penulis dapat terus memperbarui draf hingga siap dipublikasikan.
- Saat siap dipublikasikan, Fungsi Firebase dipicu yang membuat dokumen baru dalam koleksi
published
. - Draf dapat dihapus oleh penulis atau oleh moderator situs
Posting blog yang dipublikasikan:
- Posting yang dipublikasikan tidak dapat dibuat oleh pengguna, hanya melalui fungsi.
- Mereka hanya dapat dihapus secara halus, yang memperbarui atribut
visible
menjadi salah.
Komentar
- Postingan yang dipublikasikan mengizinkan komentar, yang merupakan subkoleksi pada setiap postingan yang dipublikasikan.
- Untuk mengurangi penyalahgunaan, pengguna harus memiliki alamat email terverifikasi dan tidak menolak untuk memberikan komentar.
- Komentar hanya dapat diperbarui dalam waktu satu jam setelah diposting.
- Komentar dapat dihapus oleh penulis komentar, penulis postingan asli, atau oleh moderator.
Selain aturan akses, Anda akan membuat Aturan Keamanan yang memberlakukan bidang wajib dan validasi data.
Semuanya akan terjadi secara lokal, menggunakan Firebase Emulator Suite.
Dapatkan kode sumber
Dalam codelab ini, Anda akan memulai dengan pengujian untuk Aturan Keamanan, tetapi Aturan Keamanan itu sendiri mimimal, jadi hal pertama yang perlu Anda lakukan adalah menggandakan sumber untuk menjalankan pengujian:
$ git clone https://github.com/FirebaseExtended/codelab-rules.git
Kemudian pindah ke direktori status awal, tempat Anda akan bekerja untuk sisa codelab ini:
$ cd codelab-rules/initial-state
Sekarang, instal dependensi sehingga Anda dapat menjalankan pengujian. Jika koneksi internet Anda lambat, ini mungkin memerlukan waktu satu atau dua menit:
# Move into the functions directory, install dependencies, jump out. $ cd functions && npm install && cd -
Dapatkan Firebase CLI
Suite Emulator yang akan Anda gunakan untuk menjalankan pengujian adalah bagian dari Firebase CLI (antarmuka baris perintah) yang dapat dipasang di mesin Anda dengan perintah berikut:
$ npm install -g firebase-tools
Selanjutnya, konfirmasikan bahwa Anda memiliki CLI versi terbaru. Codelab ini seharusnya berfungsi dengan versi 8.4.0 atau lebih tinggi, tetapi versi yang lebih baru menyertakan lebih banyak perbaikan bug.
$ firebase --version 9.10.2
3. Jalankan tes
Di bagian ini, Anda akan menjalankan pengujian secara lokal. Ini berarti saatnya untuk mem-boot Emulator Suite.
Mulai Emulator
Aplikasi yang akan Anda gunakan memiliki tiga koleksi utama Firestore: drafts
berisi postingan blog yang sedang dalam proses, koleksi published
berisi postingan blog yang telah dipublikasikan, dan comments
adalah subkoleksi dari postingan yang dipublikasikan. Repo dilengkapi dengan pengujian unit untuk Aturan Keamanan yang menentukan atribut pengguna dan kondisi lain yang diperlukan bagi pengguna untuk membuat, membaca, memperbarui, dan menghapus dokumen dalam drafts
, published
, dan koleksi comments
. Anda akan menulis Aturan Keamanan agar pengujian tersebut lulus.
Untuk memulai, database Anda dikunci: membaca dan menulis ke database ditolak secara universal, dan semua pengujian gagal. Saat Anda menulis Aturan Keamanan, tes akan lulus. Untuk melihat pengujian, buka functions/test.js
di editor Anda.
Pada baris perintah, mulai emulator menggunakan emulators:exec
dan jalankan pengujian:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"
Gulir ke bagian atas output:
$ 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 ...
Saat ini ada 9 kegagalan. Saat membuat file aturan, Anda dapat mengukur progres dengan melihat lebih banyak tes yang lulus.
4. Buat draf posting blog.
Karena akses untuk draf postingan blog sangat berbeda dengan akses untuk postingan blog yang dipublikasikan, aplikasi blogging ini menyimpan draf postingan blog dalam koleksi terpisah, /drafts
. Draf hanya dapat diakses oleh penulis atau moderator, dan memiliki validasi untuk bidang yang wajib diisi dan tidak dapat diubah.
Membuka file firestore.rules
, Anda akan menemukan file aturan default:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Pernyataan kecocokan, match /{document=**}
, menggunakan sintaks **
untuk diterapkan secara rekursif ke semua dokumen dalam subkoleksi. Dan karena berada di tingkat teratas, saat ini aturan selimut yang sama berlaku untuk semua permintaan, tidak peduli siapa yang membuat permintaan atau data apa yang mereka coba baca atau tulis.
Mulailah dengan menghapus pernyataan kecocokan paling dalam dan menggantinya dengan match /drafts/{draftID}
. (Komentar tentang struktur dokumen dapat membantu dalam aturan, dan akan disertakan dalam codelab ini; selalu opsional.)
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
}
}
}
Aturan pertama yang akan Anda tulis untuk draf akan mengontrol siapa yang dapat membuat dokumen. Dalam aplikasi ini, draf hanya dapat dibuat oleh orang yang terdaftar sebagai penulis. Pastikan UID orang yang mengajukan permintaan adalah UID yang sama yang tercantum dalam dokumen.
Kondisi pertama untuk pembuatan adalah:
request.resource.data.authorUID == request.auth.uid
Selanjutnya, dokumen hanya dapat dibuat jika menyertakan tiga kolom wajib, authorUID
, createdAt
, dan title
. (Pengguna tidak menyediakan bidang createdAt
; ini memaksa aplikasi harus menambahkannya sebelum mencoba membuat dokumen.) Karena Anda hanya perlu memeriksa apakah atribut sedang dibuat, Anda dapat memeriksa apakah request.resource
memiliki semua kunci-kunci itu:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
Persyaratan terakhir untuk membuat entri blog adalah panjang judul tidak boleh lebih dari 50 karakter:
request.resource.data.title.size() < 50
Karena semua kondisi ini harus benar, gabungkan ini bersama dengan operator logika AND, &&
. Aturan pertama menjadi:
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;
}
}
}
Di terminal, jalankan kembali pengujian dan konfirmasikan bahwa pengujian pertama berhasil.
5. Perbarui draf posting blog.
Selanjutnya, saat penulis menyempurnakan draf posting blog mereka, mereka akan mengedit draf dokumen. Buat aturan untuk ketentuan kapan postingan dapat diperbarui. Pertama, hanya penulis yang dapat memperbarui drafnya. Perhatikan bahwa di sini Anda memeriksa UID yang sudah ditulis, resource.data.authorUID
:
resource.data.authorUID == request.auth.uid
Persyaratan kedua untuk pembaruan adalah bahwa dua atribut, authorUID
dan createdAt
tidak boleh berubah:
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]);
Dan terakhir, judul harus terdiri dari 50 karakter atau kurang:
request.resource.data.title.size() < 50;
Karena semua kondisi ini harus dipenuhi, gabungkan dengan &&
:
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;
Aturan lengkapnya menjadi:
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;
}
}
}
Jalankan ulang pengujian Anda dan konfirmasikan bahwa pengujian lain berhasil.
6. Hapus dan baca draf: Kontrol Akses Berbasis Atribut
Sama seperti penulis yang dapat membuat dan memperbarui draf, mereka juga dapat menghapus draf.
resource.data.authorUID == request.auth.uid
Selain itu, penulis dengan atribut isModerator
pada token autentikasinya diizinkan untuk menghapus draf:
request.auth.token.isModerator == true
Karena salah satu dari kondisi ini cukup untuk penghapusan, gabungkan dengan operator logika OR, ||
:
allow delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Ketentuan yang sama berlaku untuk membaca, sehingga izin dapat ditambahkan ke aturan:
allow read, delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Aturan lengkapnya sekarang:
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;
}
}
}
Jalankan ulang pengujian Anda dan konfirmasikan bahwa pengujian lain sekarang lulus.
7. Membaca, membuat, dan menghapus posting yang diterbitkan: denormalisasi untuk pola akses yang berbeda
Karena pola akses untuk postingan yang dipublikasikan dan draf postingan sangat berbeda, aplikasi ini mendenormalisasi postingan menjadi draft
terpisah dan koleksi published
. Misalnya, posting yang diterbitkan dapat dibaca oleh siapa saja tetapi tidak dapat dihapus secara paksa, sementara draf dapat dihapus tetapi hanya dapat dibaca oleh penulis dan moderator. Di aplikasi ini, saat pengguna ingin menerbitkan draf postingan blog, sebuah fungsi dipicu yang akan membuat postingan baru yang dipublikasikan.
Selanjutnya, Anda akan menulis aturan untuk postingan yang dipublikasikan. Aturan paling sederhana untuk menulis adalah postingan yang diterbitkan dapat dibaca oleh siapa saja, dan tidak dapat dibuat atau dihapus oleh siapa pun. Tambahkan aturan ini:
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;
}
Menambahkan ini ke aturan yang ada, seluruh file aturan menjadi:
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;
}
}
}
Jalankan kembali pengujian, dan konfirmasikan bahwa pengujian lain berhasil.
8. Memperbarui posting yang diterbitkan: Fungsi khusus dan variabel lokal
Ketentuan untuk memperbarui posting yang diterbitkan adalah:
- itu hanya dapat dilakukan oleh penulis atau moderator, dan
- itu harus berisi semua bidang yang diperlukan.
Karena Anda telah menulis ketentuan untuk menjadi penulis atau moderator, Anda dapat menyalin dan menempelkan ketentuan tersebut, tetapi lama kelamaan akan menjadi sulit untuk dibaca dan dipertahankan. Sebagai gantinya, Anda akan membuat fungsi kustom yang merangkum logika sebagai penulis atau moderator. Kemudian, Anda akan memanggilnya dari beberapa kondisi.
Buat fungsi kustom
Di atas pernyataan kecocokan untuk draf, buat fungsi baru yang disebut isAuthorOrModerator
yang menggunakan dokumen pos sebagai argumen (ini akan berfungsi baik untuk draf atau pos yang dipublikasikan) dan objek autentikasi pengguna:
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: ...
}
}
}
Gunakan variabel lokal
Di dalam fungsi, gunakan kata kunci let
untuk menyetel variabel isAuthor
dan isModerator
. Semua fungsi harus diakhiri dengan pernyataan kembali, dan kami akan mengembalikan boolean yang menunjukkan jika salah satu variabel benar:
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
Panggil fungsinya
Sekarang Anda akan memperbarui aturan draf untuk memanggil fungsi tersebut, berhati-hatilah untuk meneruskan resource.data
sebagai argumen pertama:
// Draft blog posts
match /drafts/{draftID} {
...
// Can be deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
Sekarang Anda dapat menulis syarat untuk memperbarui posting yang diterbitkan yang juga menggunakan fungsi baru:
allow update: if isAuthorOrModerator(resource.data, request.auth);
Tambahkan validasi
Beberapa kolom dari postingan yang diterbitkan tidak boleh diubah, khususnya url
, authorUID
, dan publishedAt
tidak dapat diubah. Dua bidang lainnya, title
dan content
, dan visible
harus tetap ada setelah pembaruan. Tambahkan ketentuan untuk menerapkan persyaratan ini untuk pembaruan pada postingan yang dipublikasikan:
// 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"
])
Buat fungsi kustom sendiri
Dan terakhir, tambahkan syarat bahwa judul harus di bawah 50 karakter. Karena ini adalah logika yang digunakan kembali, Anda dapat melakukannya dengan membuat fungsi baru, titleIsUnder50Chars
. Dengan fungsi baru, syarat untuk memperbarui postingan yang dipublikasikan menjadi:
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);
Dan file aturan lengkapnya adalah:
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);
}
}
}
Jalankan kembali tes. Pada titik ini, Anda harus memiliki 5 tes kelulusan dan 4 tes gagal.
9. Komentar: Subkoleksi dan izin penyedia masuk
Posting yang diterbitkan memungkinkan komentar, dan komentar disimpan dalam subkoleksi dari posting yang diterbitkan ( /published/{postID}/comments/{commentID}
). Secara default, aturan koleksi tidak berlaku untuk subkoleksi. Anda tidak ingin aturan yang sama yang berlaku untuk dokumen induk dari kiriman yang diterbitkan berlaku untuk komentar; Anda akan membuat yang berbeda.
Untuk menulis aturan untuk mengakses komentar, mulailah dengan pernyataan kecocokan:
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `comment`: string, < 500 characters, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
Membaca komentar: Tidak boleh anonim
Untuk aplikasi ini, hanya pengguna yang telah membuat akun permanen, bukan akun anonim yang dapat membaca komentar. Untuk menerapkan aturan itu, cari atribut sign_in_provider
yang ada di setiap objek auth.token
:
allow read: if request.auth.token.firebase.sign_in_provider != "anonymous";
Jalankan kembali pengujian Anda, dan konfirmasikan bahwa satu pengujian lagi berhasil.
Membuat komentar: Memeriksa daftar tolak
Ada tiga syarat untuk membuat komentar:
- pengguna harus memiliki email terverifikasi
- komentar harus kurang dari 500 karakter, dan
- mereka tidak bisa masuk dalam daftar pengguna yang dilarang, yang disimpan di firestore dalam koleksi
bannedUsers
. Mengambil kondisi ini satu per satu:
request.auth.token.email_verified == true
request.resource.data.comment.size() < 500
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
Aturan terakhir untuk membuat komentar adalah:
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));
Seluruh file aturan sekarang:
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 charachters
request.resource.data.comment.size() < 500 &&
// UID is not on the block list
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
}
}
}
Jalankan kembali tes, dan pastikan satu tes lagi lulus.
10. Memperbarui komentar: Aturan berbasis waktu
Logika bisnis untuk komentar adalah bahwa komentar dapat diedit oleh penulis komentar selama satu jam setelah dibuat. Untuk mengimplementasikannya, gunakan stempel waktu createdAt
.
Pertama, untuk menetapkan bahwa pengguna adalah penulis:
request.auth.uid == resource.data.authorUID
Selanjutnya, komentar itu dibuat dalam satu jam terakhir:
(request.time - resource.data.createdAt) < duration.value(1, 'h');
Menggabungkannya dengan operator logika AND, aturan untuk memperbarui komentar menjadi:
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');
Jalankan kembali tes, dan pastikan satu tes lagi lulus.
11. Menghapus komentar: memeriksa kepemilikan orang tua
Komentar dapat dihapus oleh penulis komentar, moderator, atau penulis postingan blog.
Pertama, karena fungsi pembantu yang Anda tambahkan sebelumnya memeriksa bidang authorUID
yang mungkin ada di kiriman atau komentar, Anda dapat menggunakan kembali fungsi pembantu untuk memeriksa apakah pengguna adalah penulis atau moderator:
isAuthorOrModerator(resource.data, request.auth)
Untuk memeriksa apakah pengguna adalah penulis postingan blog, gunakan get
untuk mencari postingan di Firestore:
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID
Karena salah satu dari kondisi ini sudah cukup, gunakan operator logika OR di antaranya:
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;
Jalankan kembali tes, dan pastikan satu tes lagi lulus.
Dan seluruh file aturan adalah:
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 charachters
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. Langkah selanjutnya
Selamat! Anda telah menulis Aturan Keamanan yang membuat semua tes lulus dan mengamankan aplikasi!
Berikut beberapa topik terkait untuk dipelajari selanjutnya:
- Entri blog : Cara meninjau kode Aturan Keamanan
- Codelab : menelusuri pengembangan lokal pertama dengan Emulator
- Video : Cara menggunakan penyiapan CI untuk pengujian berbasis emulator menggunakan GitHub Actions