1. Başlamadan önce
Cloud Firestore, Cloud Storage for Firebase ve Realtime Database, okuma ve yazma erişimi vermek için yazdığınız yapılandırma dosyalarına güvenir. Güvenlik Kuralları adı verilen bu yapılandırma, uygulamanız için bir tür şema görevi de görebilir. Uygulamanızı geliştirmenin en önemli kısımlarından biridir. Ve bu codelab size yol gösterecektir.
Önkoşullar
- Visual Studio Code, Atom veya Sublime Text gibi basit bir düzenleyici
- Node.js 8.6.0 veya üzeri (Node.js'yi yüklemek için nvm ; sürümünüzü kontrol etmek için
node --version
çalıştırın) - Java 7 veya üzeri (Java'yı yüklemek için bu talimatları kullanın ; sürümünüzü kontrol etmek için
java -version
çalıştırın)
ne yapacaksın
Bu kod laboratuvarında, Firestore üzerine kurulmuş basit bir blog platformunu güvence altına alacaksınız. Güvenlik Kurallarına göre birim testleri çalıştırmak için Firestore öykünücüsünü kullanacak ve kuralların beklediğiniz erişime izin verip vermediğinden emin olacaksınız.
Nasıl yapılacağını öğreneceksiniz:
- Parçalı izinler verme
- Verileri ve tür doğrulamalarını zorunlu kılın
- Nitelik Tabanlı Erişim Kontrolü Uygulayın
- Kimlik doğrulama yöntemine göre erişim izni verin
- Özel işlevler oluşturun
- Zamana dayalı Güvenlik Kuralları oluşturun
- Reddetme listesi ve geçici silme işlemleri uygulayın
- Birden çok erişim modelini karşılamak için verilerin ne zaman normalleştirilmesi gerektiğini anlayın
2. Kurulum
Bu bir blog uygulamasıdır. İşte uygulama işlevselliğinin üst düzey bir özeti:
Taslak blog gönderileri:
- Kullanıcılar,
drafts
koleksiyonunda yer alan taslak blog gönderileri oluşturabilir. - Yazar, yayınlanmaya hazır olana kadar bir taslağı güncellemeye devam edebilir.
- Yayınlanmaya hazır olduğunda,
published
koleksiyonda yeni bir belge oluşturan bir Firebase İşlevi tetiklenir. - Taslaklar, yazar veya site moderatörleri tarafından silinebilir.
Yayınlanan blog yazıları:
- Yayınlanan gönderiler kullanıcılar tarafından oluşturulamaz, yalnızca bir işlev aracılığıyla oluşturulur.
- Yalnızca geçici olarak silinebilirler, bu da
visible
bir özniteliği false olarak günceller.
Yorumlar
- Yayınlanan gönderiler, yayınlanan her gönderide bir alt koleksiyon olan yorumlara izin verir.
- Kötüye kullanımı azaltmak için, kullanıcıların doğrulanmış bir e-posta adresine sahip olmaları ve yorum bırakabilmeleri için red listesinde bulunmamaları gerekir.
- Yorumlar, yayınlandıktan sonra yalnızca bir saat içinde güncellenebilir.
- Yorumlar, yorum yazarı, orijinal gönderinin yazarı veya moderatörler tarafından silinebilir.
Erişim kurallarına ek olarak, gerekli alanları ve veri doğrulamalarını zorunlu kılan Güvenlik Kuralları oluşturacaksınız.
Firebase Emulator Suite kullanılarak her şey yerel olarak gerçekleşecek.
Kaynak kodunu al
Bu codelab'de, Güvenlik Kuralları için testlerle başlayacaksınız, ancak Güvenlik Kuralları minimum düzeyde olacak, bu nedenle yapmanız gereken ilk şey, testleri çalıştırmak için kaynağı klonlamaktır:
$ git clone https://github.com/FirebaseExtended/codelab-rules.git
Ardından, bu codelab'in geri kalanında çalışacağınız ilk durum dizinine geçin:
$ cd codelab-rules/initial-state
Şimdi, testleri çalıştırabilmeniz için bağımlılıkları kurun. Daha yavaş bir internet bağlantınız varsa bu işlem bir veya iki dakika sürebilir:
# Move into the functions directory, install dependencies, jump out. $ cd functions && npm install && cd -
Firebase CLI'yi edinin
Testleri çalıştırmak için kullanacağınız Emulator Suite, aşağıdaki komutla makinenize kurulabilen Firebase CLI'nin (komut satırı arayüzü) bir parçasıdır:
$ npm install -g firebase-tools
Ardından, CLI'nin en son sürümüne sahip olduğunuzu onaylayın. Bu codelab, 8.4.0 veya sonraki sürümlerle çalışmalıdır, ancak sonraki sürümler daha fazla hata düzeltmesi içerir.
$ firebase --version 9.10.2
3. Testleri çalıştırın
Bu bölümde, testleri yerel olarak çalıştıracaksınız. Bu, Emulator Suite'i başlatma zamanının geldiği anlamına gelir.
Emülatörleri Başlat
Çalışacağınız uygulamanın üç ana Firestore koleksiyonu vardır: drafts
, devam etmekte olan blog gönderilerini içerir, published
koleksiyon, yayınlanmış blog gönderilerini içerir ve comments
, yayınlanan gönderilerin bir alt koleksiyonudur. Depo, bir kullanıcının drafts
, published
ve comments
koleksiyonlarındaki belgeleri oluşturması, okuması, güncellemesi ve silmesi için gerekli olan kullanıcı özniteliklerini ve diğer koşulları tanımlayan Güvenlik Kuralları için birim testleriyle birlikte gelir. Bu testleri geçmek için Güvenlik Kurallarını yazacaksınız.
Başlamak için veritabanınız kilitlendi: veritabanına okuma ve yazma işlemleri evrensel olarak reddedildi ve tüm testler başarısız oldu. Güvenlik Kurallarını yazdıkça testler geçecektir. Testleri görmek için editörünüzde functions/test.js
dosyasını açın.
Komut satırında, emulators:exec
kullanarak emülatörleri başlatın ve testleri çalıştırın:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"
Çıktının en üstüne gidin:
$ 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 ...
Şu anda 9 arıza var. Kural dosyasını oluştururken, daha fazla testin geçtiğini izleyerek ilerlemeyi ölçebilirsiniz.
4. Blog yazısı taslakları oluşturun.
Taslak blog gönderilerine erişim, yayınlanan blog gönderilerine erişimden çok farklı olduğundan, bu blog uygulaması, taslak blog gönderilerini ayrı bir koleksiyonda saklar /drafts
. Taslaklara yalnızca yazar veya moderatör tarafından erişilebilir ve gerekli ve değişmez alanlar için doğrulamaları vardır.
firestore.rules
dosyasını açtığınızda, bir varsayılan kurallar dosyası bulacaksınız:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
match deyimi, match /{document=**}
, alt koleksiyonlardaki tüm belgelere yinelemeli olarak uygulamak için **
sözdizimini kullanıyor. Ve en üst düzeyde olduğu için, isteği kimin yaptığına veya hangi verileri okumaya veya yazmaya çalıştıklarına bakılmaksızın, şu anda tüm istekler için aynı genel kural geçerlidir.
En içteki match deyimini kaldırıp yerine match /drafts/{draftID}
ile başlayın. (Belgelerin yapısına ilişkin yorumlar, kurallarda yardımcı olabilir ve bu kod laboratuvarına dahil edilecektir; bunlar her zaman isteğe bağlıdır.)
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
}
}
}
Taslaklar için yazacağınız ilk kural, belgeleri kimlerin oluşturabileceğini kontrol edecektir. Bu uygulamada taslaklar yalnızca yazar olarak listelenen kişi tarafından oluşturulabilir. Talepte bulunan kişinin UID'sinin belgede listelenen UID ile aynı olduğunu kontrol edin.
Oluşturmanın ilk koşulu şöyle olacaktır:
request.resource.data.authorUID == request.auth.uid
Daha sonra, belgeler yalnızca üç gerekli alanı, authorUID
, createdAt
ve title
içermeleri halinde oluşturulabilir. (Kullanıcı createdAt
alanını sağlamaz; bu, uygulamanın bir belge oluşturmaya çalışmadan önce bunu eklemesi gerektiğini zorunlu kılar.) Yalnızca özniteliklerin oluşturulduğunu kontrol etmeniz gerektiğinden, request.resource
tüm özelliklere sahip olduğunu kontrol edebilirsiniz. bu anahtarlar:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
Bir blog gönderisi oluşturmak için son koşul, başlığın 50 karakterden uzun olmamasıdır:
request.resource.data.title.size() < 50
Tüm bu koşulların doğru olması gerektiğinden, bunları mantıksal AND işleci &&
ile birleştirin. İlk kural şöyle olur:
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;
}
}
}
Terminalde testleri yeniden çalıştırın ve ilk testin geçtiğini onaylayın.
5. Blog yazısı taslaklarını güncelleyin.
Ardından, yazarlar taslak blog gönderilerini iyileştirdikçe taslak belgeleri düzenlerler. Bir gönderinin güncellenebileceği koşullar için bir kural oluşturun. İlk olarak, yalnızca yazar taslaklarını güncelleyebilir. Burada zaten yazılmış olan UID'yi kontrol ettiğinizi unutmayın, resource.data.authorUID
:
resource.data.authorUID == request.auth.uid
Bir güncelleme için ikinci gereklilik, iki özniteliğin, authorUID
ve createdAt
değişmemesidir:
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]);
Son olarak, başlık en fazla 50 karakter olmalıdır:
request.resource.data.title.size() < 50;
Bu koşulların hepsinin karşılanması gerektiğinden, bunları &&
ile birleştirin:
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;
Tam kurallar şöyle olur:
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;
}
}
}
Testlerinizi yeniden çalıştırın ve başka bir testin geçtiğini onaylayın.
6. Taslakları silin ve okuyun: Nitelik Tabanlı Erişim Kontrolü
Yazarlar taslak oluşturup güncelleyebildikleri gibi taslakları da silebilir.
resource.data.authorUID == request.auth.uid
Ek olarak, auth belirteçlerinde isModerator
özniteliğine sahip yazarların taslakları silmesine izin verilir:
request.auth.token.isModerator == true
Bu koşullardan herhangi biri silme işlemi için yeterli olduğundan, bunları mantıksal bir VEYA işleci ||
:
allow delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Aynı koşullar okumalar için de geçerlidir, böylece kurala izin eklenebilir:
allow read, delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Tam kurallar şimdi:
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;
}
}
}
Testlerinizi yeniden çalıştırın ve başka bir testin şimdi geçtiğini onaylayın.
7. Yayınlanan gönderileri okur, oluşturur ve siler: farklı erişim kalıpları için denormalize etme
Yayınlanan gönderiler ve taslak gönderiler için erişim kalıpları çok farklı olduğundan, bu uygulama gönderileri normalden çıkarıp ayrı draft
ve published
koleksiyonlara ayırır. Örneğin, yayınlanan gönderiler herkes tarafından okunabilir ancak kalıcı olarak silinemezken, taslaklar silinebilir ancak yalnızca yazar ve moderatörler tarafından okunabilir. Bu uygulamada, bir kullanıcı taslak bir blog gönderisi yayınlamak istediğinde, yayınlanan yeni gönderiyi oluşturacak bir işlev tetiklenir.
Ardından, yayınlanan gönderiler için kuralları yazacaksınız. Yazılması gereken en basit kurallar, yayınlanan gönderilerin herkes tarafından okunabilmesi ve hiç kimse tarafından oluşturulamaması veya silinememesidir. Bu kuralları ekleyin:
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;
}
Bunları mevcut kurallara eklediğinizde, kural dosyasının tamamı şu hale gelir:
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;
}
}
}
Testleri yeniden çalıştırın ve başka bir testin geçtiğini onaylayın.
8. Yayınlanan gönderileri güncelleme: Özel işlevler ve yerel değişkenler
Yayınlanmış bir gönderiyi güncelleme koşulları şunlardır:
- yalnızca yazar veya moderatör tarafından yapılabilir ve
- gerekli tüm alanları içermelidir.
Yazar veya moderatör olmak için zaten koşullar yazdığınızdan, koşulları kopyalayıp yapıştırabilirsiniz, ancak zamanla bunları okumak ve sürdürmek zorlaşabilir. Bunun yerine, yazar veya moderatör olma mantığını özetleyen özel bir işlev oluşturacaksınız. Ardından, onu birden fazla koşuldan arayacaksınız.
Özel bir işlev oluşturun
Taslaklar için eşleştirme ifadesinin üzerinde, isAuthorOrModerator
adında, bir gönderi belgesini (bu, taslaklar veya yayınlanmış gönderiler için çalışır) ve kullanıcının auth nesnesini bağımsız değişken olarak alan yeni bir işlev oluşturun:
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: ...
}
}
}
Yerel değişkenleri kullan
İşlev içinde, isAuthor
ve isModerator
değişkenlerini ayarlamak için let
anahtar sözcüğünü kullanın. Tüm işlevler bir dönüş ifadesiyle bitmelidir ve bizimki, değişkenlerden birinin doğru olup olmadığını gösteren bir boole döndürür:
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
işlevi çağır
Şimdi, ilk bağımsız değişken olarak resource.data
iletmeye dikkat ederek, bu işlevi çağırmak için taslaklar kuralını güncelleyeceksiniz:
// Draft blog posts
match /drafts/{draftID} {
...
// Can be deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
Artık, yeni işlevi de kullanan yayınlanmış gönderileri güncellemek için bir koşul yazabilirsiniz:
allow update: if isAuthorOrModerator(resource.data, request.auth);
Doğrulama ekle
Yayınlanmış bir gönderinin bazı alanları değiştirilmemelidir, özellikle url
, authorUID
publishedAt
alanları sabittir. Diğer iki alan, title
ve content
ve visible
bir güncellemeden sonra da mevcut olmalıdır. Yayınlanan gönderilerdeki güncellemeler için bu gereksinimleri uygulamak üzere koşullar ekleyin:
// 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"
])
Kendi başınıza özel bir işlev oluşturun
Ve son olarak, başlığın 50 karakterin altında olması koşulunu ekleyin. Bu yeniden kullanılan bir mantık olduğundan, bunu, titleIsUnder50Chars
adlı yeni bir işlev oluşturarak yapabilirsiniz. Yeni işlevle, yayınlanan bir gönderiyi güncelleme koşulu şu hale gelir:
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);
Ve tam kural dosyası:
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);
}
}
}
Testleri tekrar çalıştırın. Bu noktada, 5 geçme ve 4 başarısız olmanız gerekir.
9. Yorumlar: Alt koleksiyonlar ve oturum açma sağlayıcı izinleri
Yayınlanan gönderiler yorumlara izin verir ve yorumlar yayınlanan gönderinin bir alt koleksiyonunda saklanır ( /published/{postID}/comments/{commentID}
). Varsayılan olarak bir koleksiyonun kuralları alt koleksiyonlar için geçerli değildir. Yayınlanan gönderinin üst belgesi için geçerli olan kuralların yorumlara da uygulanmasını istemezsiniz; farklı olanları yapacaksın.
Yorumlara erişim kuralları yazmak için, eşleşme ifadesiyle başlayın:
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `comment`: string, < 500 characters, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
Yorumları okumak: Anonim olamaz
Bu uygulama için, anonim bir hesap değil, yalnızca kalıcı bir hesap oluşturan kullanıcılar yorumları okuyabilir. Bu kuralı uygulamak için, her auth.token
nesnesinde bulunan sign_in_provider
özniteliğine bakın:
allow read: if request.auth.token.firebase.sign_in_provider != "anonymous";
Testlerinizi yeniden çalıştırın ve bir testin daha geçtiğini onaylayın.
Yorum oluşturma: Reddetme listesini kontrol etme
Yorum oluşturmak için üç koşul vardır:
- bir kullanıcının doğrulanmış bir e-postası olmalıdır
- yorum 500 karakterden az olmalı ve
-
bannedUsers
deposunda banli Kullanıcılar koleksiyonunda depolanan yasaklı kullanıcılar listesinde olamazlar. Bu koşulları birer birer ele alarak:
request.auth.token.email_verified == true
request.resource.data.comment.size() < 500
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
Yorum oluşturmak için son kural şudur:
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));
Tüm kurallar dosyası şimdi:
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));
}
}
}
Testleri yeniden çalıştırın ve bir testin daha geçtiğinden emin olun.
10. Yorumları güncelleme: Zamana dayalı kurallar
Yorumlar için iş mantığı, yorum yazarı tarafından oluşturulduktan sonra bir saat boyunca düzenlenebilmesidir. Bunu uygulamak için, createdAt
zaman damgasını kullanın.
İlk olarak, kullanıcının yazar olduğunu belirlemek için:
request.auth.uid == resource.data.authorUID
Ardından, yorumun son bir saat içinde oluşturulduğu:
(request.time - resource.data.createdAt) < duration.value(1, 'h');
Bunları mantıksal AND operatörüyle birleştirerek, yorumları güncelleme kuralı şu hale gelir:
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');
Testleri yeniden çalıştırın ve bir testin daha geçtiğinden emin olun.
11. Yorumları silme: ebeveyn sahipliğini kontrol etme
Yorumlar, yorum yazarı, moderatör veya blog gönderisinin yazarı tarafından silinebilir.
İlk olarak, daha önce eklediğiniz yardımcı işlev, bir gönderide veya yorumda bulunabilecek bir authorUID
alanını kontrol ettiğinden, kullanıcının yazar mı yoksa moderatör mü olduğunu kontrol etmek için yardımcı işlevi yeniden kullanabilirsiniz:
isAuthorOrModerator(resource.data, request.auth)
Kullanıcının blog gönderisinin yazarı olup olmadığını kontrol etmek için get
kullanarak Firestore'da gönderiyi arayın:
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID
Bu koşullardan herhangi biri yeterli olduğundan, aralarında mantıksal bir VEYA işleci kullanın:
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;
Testleri yeniden çalıştırın ve bir testin daha geçtiğinden emin olun.
Ve tüm kurallar dosyası:
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. Sonraki adımlar
Tebrikler! Tüm testlerin geçmesini sağlayan ve uygulamayı güvence altına alan Güvenlik Kurallarını yazdınız!
Sırada incelenecek bazı ilgili konular şunlardır:
- Blog gönderisi : Kod incelemesi Güvenlik Kuralları nasıl yapılır?
- Codelab : Öykünücüler ile yerel ilk geliştirmeyi gözden geçirmek
- Video : GitHub Eylemlerini kullanarak öykünücü tabanlı testler için CI kurulumu nasıl kullanılır?