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ını kullanır. Güvenlik Kuralları adı verilen bu yapılandırma, uygulamanız için bir tür şema işlevi de kullanabilir. Uygulamanızı geliştirmenin en önemli bölümlerinden biridir. Bu codelab'de süreç adım adım açıklanmaktadır.
Ön koşullar
- Visual Studio Code, Atom veya Sublime Text gibi basit bir düzenleyici
- Node.js 8.6.0 veya sonraki sürümleri (Node.js'yi yüklemek için nvm kullanın; sürümünüzü kontrol etmek için
node --version
komutunu çalıştırın) - Java 7 veya üstü (Java'yı yüklemek için bu talimatları uygulayın; sürümünüzü kontrol etmek için
java -version
komutunu çalıştırın)
Yapacaklarınız
Bu codelab'de, Firestore'da oluşturulmuş basit bir blog platformunu güvenli hale getireceksiniz. Güvenlik Kurallarına göre birim testleri çalıştırmak için Firestore emülatörünü kullanacaksınız ve kuralların beklediğiniz erişime izin verip vermediğinden emin olmalısınız.
Öğrenecekleriniz:
- Ayrıntılı izinler verin
- Veri ve tür doğrulamalarını zorunlu kılma
- Özellik Tabanlı Erişim Denetimi Uygulama
- Kimlik doğrulama yöntemine göre erişim izni ver
- Özel işlevler oluşturma
- Zamana Dayalı Güvenlik Kuralları oluşturma
- Reddetme listesi ve geri yüklenebilir şekilde silme özellikleri uygulama
- Birden çok erişim kalıbını karşılamak için verilerin ne zaman normalleştirileceğini anlama
2. Ayarla
Bu bir blog uygulamasıdır. Aşağıda, uygulama işlevinin genel bir özeti verilmiştir:
Taslak blog yayınları:
- Kullanıcılar,
drafts
koleksiyonunda yer alan taslak blog yayınları oluşturabilir. - Yazar, yayınlanmaya hazır olana kadar taslak güncellemeye devam edebilir.
- Dosya yayınlanmaya hazır olduğunda,
published
koleksiyonunda yeni bir doküman oluşturan bir Firebase İşlevi tetiklenir. - Taslaklar yazar veya site moderatörleri tarafından silinebilir
Yayınlanan blog yayınları:
- Yayınlanan yayınlar kullanıcılar tarafından değil, yalnızca bir işlev aracılığıyla oluşturulabilir.
- Yalnızca geri yüklenebilir şekilde silinebilirler. Bu durumda
visible
özelliği false olarak güncellenir.
Yorumlar
- Yayınlanan yayınlar, yayınlanan her yayında bir alt koleksiyon olan yorumlara izin verir.
- Kötüye kullanımı azaltmak amacıyla, kullanıcıların yorum bırakmak için doğrulanmış bir e-posta adresine sahip olmaları ve ret listesinde bulunmamaları gerekir.
- Yorumlar, yayınlandıktan sonra yalnızca bir saat içinde güncellenebilir.
- Yorumlar; yorumun yazarı, orijinal yayının yazarı veya moderatörler tarafından silinebilir.
Erişim kurallarına ek olarak, zorunlu alanları ve veri doğrulamalarını zorunlu kılan Güvenlik Kuralları da oluşturacaksınız.
Firebase Emulator Suite sayesinde her şey yerel olarak gerçekleştirilir.
Kaynak kodunu alma
Bu codelab'de, Güvenlik Kuralları testleriyle başlayacaksınız ancak Güvenlik Kurallarını taklit edeceksiniz. 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 kalanı için çalışacağınız ilk durum dizinine geçin:
$ cd codelab-rules/initial-state
Şimdi testleri çalıştırabilmek için bağımlılıkları yükleyin. 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'ı edinme
Testleri çalıştırmak için kullanacağınız Emulator Suite, Firebase CLI'ın (komut satırı arayüzü) bir parçasıdır. Bu komut satırı arayüzü, aşağıdaki komutla makinenize yüklenebilir:
$ npm install -g firebase-tools
Ardından, KSA'nın en son sürümüne sahip olduğunuzu onaylayın. Bu codelab'in 8.4.0 veya sonraki sürümlerde çalışması gerekir. Ancak sonraki sürümlerde daha fazla hata düzeltmesi bulunmaktadır.
$ firebase --version 9.10.2
3. Testleri yapın
Bu bölümde testleri yerel olarak çalıştıracaksınız. Emulator Suite'i başlatma zamanı geldi.
Emülatörleri başlatın
Birlikte çalışacağınız uygulamanın üç ana Firestore koleksiyonu vardır: drafts
devam etmekte olan blog yayınlarını içerir, published
koleksiyonu yayınlanmış blog yayınlarını içerir ve comments
, yayınlanmış yayınlardaki bir alt koleksiyondur. Depo, kullanıcının drafts
, published
ve comments
koleksiyonlarında doküman oluşturması, okuması, güncellemesi ve silmesi için gereken kullanıcı özelliklerini ve diğer koşulları tanımlayan Güvenlik Kuralları için birim testleriyle birlikte gelir. Bu testlerin başarılı olmasını sağlamak için Güvenlik Kuralları yazacaksınız.
Öncelikle veritabanınız kilitlendi: Veritabanındaki okuma ve yazma işlemleri evrensel olarak reddedilir ve tüm testler başarısız olur. Güvenlik Kuralları yazdıkça testler başarılı olur. Testleri görmek için düzenleyicinizde functions/test.js
dosyasını açın.
Komut satırında emulators:exec
komutunu kullanarak emülatörleri başlatın ve testleri çalıştırın:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"
Çıkışı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 hata var. Kural dosyasını oluştururken, başarılı olan daha fazla testi izleyerek ilerlemeyi ölçebilirsiniz.
4. Blog yayını taslakları oluşturma
Taslak blog yayınlarına erişim, yayınlanan blog yayınlarına erişimden çok farklı olduğundan, bu blog uygulaması taslak blog yayınlarını ayrı bir koleksiyonda (/drafts
) depolar. Taslaklara yalnızca yazar veya moderatörler erişebilir. Bu taslaklar, zorunlu ve değiştirilemez alanlar için doğrulama içerir.
firestore.rules
dosyasını açtığınızda varsayılan kural dosyası görüntülenir:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Eşleşme ifadesi (match /{document=**}
), alt koleksiyonlardaki tüm dokümanlara yinelenen şekilde uygulamak için **
söz dizimini kullanıyor. En üst düzeyde olduğu için, şu anda aynı genel kural, isteği kimin yaptığı veya hangi verileri okumaya ya da yazmaya çalıştığına bakılmaksızın tüm istekler için geçerlidir.
İlk olarak, en içteki eşleşme ifadesini kaldırıp match /drafts/{draftID}
ile değiştirin. (Belge yapısıyla ilgili yorumlar, kurallarda yararlı olabilir ve bu codelab'e dahil edilecektir. 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, dokümanları kimlerin oluşturabileceğini belirler. Bu uygulamada, taslaklar yalnızca yazar olarak listelenen kişi tarafından oluşturulabilir. İsteği yapan kişinin UID'sinin, dokümanda listelenen UID ile aynı olduğundan emin olun.
Oluşturmanın ilk koşulu şu şekilde olur:
request.resource.data.authorUID == request.auth.uid
Daha sonra, dokümanlar yalnızca üç zorunlu alanı (authorUID
, createdAt
ve title
) içermeleri halinde oluşturulabilir. (createdAt
alanını kullanıcı sağlamaz. Bu, uygulamanın bir doküman oluşturmaya çalışmadan önce bu alanı eklemesi zorunluluğunu zorunlu kılar.) Yalnızca özelliklerin oluşturulmakta olduğunu kontrol etmeniz gerektiğinden bu anahtarların request.resource
tarafından sağlanıp sağlanmadığını kontrol edebilirsiniz:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
Blog yayını oluşturmak için son şart, 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 operatörüyle birleştirin. İlk kural ş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;
}
}
}
Terminalde testleri yeniden çalıştırın ve ilk testin başarılı olduğunu onaylayın.
5. Blog yayını taslaklarını güncelleme.
Ardından, yazarlar taslak blog yayınlarını hassaslaştırdıkça taslak dokümanları düzenlerler. Yayının güncellenebileceği koşullar için bir kural oluşturun. İlk olarak, taslaklarını yalnızca yazar güncelleyebilir. Önceden yazılmış olan UID'yi burada kontrol edebilirsiniz.resource.data.authorUID
:
resource.data.authorUID == request.auth.uid
Güncelleme için ikinci şart, authorUID
ve createdAt
özelliklerinin 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;
Şu koşulların tümünün 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;
Tüm kurallar şu şekilde 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 tekrar çalıştırın ve başka bir testin başarılı olduğunu onaylayın.
6. Taslakları silme ve okuma: Özellik Tabanlı Erişim Denetimi
Yazarlar, taslak oluşturup güncelleyebileceği gibi taslakları da silebilir.
resource.data.authorUID == request.auth.uid
Ayrıca, yetkilendirme jetonunda isModerator
özelliği olan yazarların taslakları silmelerine izin verilir:
request.auth.token.isModerator == true
Silme işlemi için bu koşullardan herhangi biri yeterli olduğundan bunları ||
mantıksal bir OR operatörüyle birleştirin:
allow delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Okumalar için de aynı koşullar geçerlidir. Bu nedenle izin kurala eklenebilir:
allow read, delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
Kuralların tamamı artık şu şekildedir:
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 tekrar çalıştırın ve başka bir testin başarılı olduğunu doğrulayın.
7. Yayınlanan yayınlar için okuma, oluşturma ve silme işlemleri: Farklı erişim kalıpları için normalleştirmenin kaldırılması
Yayınlanan yayınlar ile taslak yayınların erişim kalıpları çok farklı olduğundan bu uygulama, yayınları normal draft
ve published
koleksiyonlarına ayırarak normalleştirir. Örneğin, yayınlanan yayınlar herkes tarafından okunabilir ancak kalıcı olarak silinemez, taslaklar silinebilir ancak yalnızca yazar ve moderatörler tarafından okunabilir. Bu uygulamada, bir kullanıcı taslak blog yayını yayınlamak istediğinde, yeni yayınlanan yayını oluşturan bir işlev tetikleniyor.
Ardından, paylaşılan yayınlara ilişkin kuralları yazın. Yazılacak en basit kurallar, yayınlanan yayınların herkes tarafından okunabilmesi ve hiç kimse tarafından oluşturulamaması ya da silinememesidir. Şu 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, kurallar dosyasının tamamı şu şekilde 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;
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 tekrar çalıştırın ve başka bir testin başarılı olduğunu doğrulayın.
8. Yayınlanmış yayınları güncelleme: Özel işlevler ve yerel değişkenler
Paylaşılan bir yayını güncelleme koşulları şunlardır:
- yalnızca yazar veya moderatör tarafından yapılabilir ve
- zorunlu tüm alanları içermelidir.
Yazar veya moderatör olmak için gerekli koşulları zaten yazdığınızdan, koşulları kopyalayıp yapıştırabilirsiniz ancak zaman içinde bu koşulların okunması ve sürdürülmesi zorlaşabilir. Bunun yerine, yazar veya moderatör olma mantığını kapsayan özel bir işlev oluşturursunuz. Ardından, birçok koşuldan çağırırsınız.
Özel işlev oluşturma
Taslaklar için eşleşme ifadesinin üzerinde, bir yayın dokümanını (taslaklar veya yayınlanmış yayınlar için kullanılabilir) bağımsız değişken olarak kabul eden isAuthorOrModerator
adlı yeni bir işlev ve kullanıcının kimlik doğrulama nesnesi 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ın
İşlevin içinde, isAuthor
ve isModerator
değişkenlerini ayarlamak için let
anahtar kelimesini kullanın. Tüm işlevler bir döndürülen ifadesiyle bitmelidir; bizimki ise 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;
}
İşlevi çağırın
Şimdi, ilk bağımsız değişken olarak resource.data
öğesini iletmeye dikkat ederek taslakların bu işlevi çağırmasına yönelik kuralı 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 yayınlanan yayınları güncellemek için yeni işlevi de kullanan bir koşul yazabilirsiniz:
allow update: if isAuthorOrModerator(resource.data, request.auth);
Doğrulama ekle
Paylaşılan bir yayının bazı alanları değiştirilmemelidir (özellikle url
, authorUID
ve publishedAt
alanları değiştirilemez). Diğer iki alan (title
, content
ve visible
) güncelleme sonrasında da mevcut olmalıdır. Yayınlanan yayınlarda yapılacak güncellemelerde bu şartları zorunlu kılmak için koşul 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"
])
Kendiniz bir özel işlev oluşturun
Son olarak, başlığın 50 karakterden kısa olması koşulunu ekleyin. Bu mantık yeniden kullanıldığı için titleIsUnder50Chars
adında yeni bir işlev oluşturarak bunu yapabilirsiniz. Bu yeni işlevle birlikte, yayınlanan bir yayını güncelleme koşulu şu şekilde olur:
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);
Kural dosyasının tamamı şu şekildedir:
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 yeniden çalıştırın. Bu noktada, geçen 5 test ve başarısız olan 4 testinizin olması gerekir.
9. Yorumlar: Alt koleksiyonlar ve oturum açma sağlayıcısı izinleri
Yayınlanan yayınlar yorumlara izin verir ve yorumlar, paylaşılan yayının alt koleksiyonunda (/published/{postID}/comments/{commentID}
) saklanır. Varsayılan olarak bir koleksiyonun kuralları, alt koleksiyonlar için geçerli değildir. Yayınlanan yayının üst dokümanı için geçerli olan kuralların yorumlara da uygulanmasını istemezsiniz; farklı resimler oluşturacaksınız.
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ı okuma: Anonim olamaz
Bu uygulama için yorumları, yalnızca kalıcı hesap oluşturan kullanıcılar okuyabilir. Bu tür hesapları anonim bir hesap göremez. Bu kuralı uygulamak için her bir auth.token
nesnesinde bulunan sign_in_provider
özelliğini arayın:
allow read: if request.auth.token.firebase.sign_in_provider != "anonymous";
Testlerinizi tekrar çalıştırın ve bir testin daha başarılı olduğunu doğrulayın.
Yorum oluşturma: Reddetme listesini kontrol etme
Yorum oluşturmanın üç koşulu vardır:
- Kullanıcının doğrulanmış bir e-posta adresine sahip olması gerekir
- Yorum 500 karakterden kısa olmalı ve
bannedUsers
koleksiyonundaki firestore'da depolanan yasaklı kullanıcılar listesinde yer alamazlar. Aşağıdaki koşulları tek tek uygulayarak:
request.auth.token.email_verified == true
request.resource.data.comment.size() < 500
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
Yorum oluşturmanın son kuralı şöyledir:
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));
Artık kurallar dosyasının tamamı:
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));
}
}
}
Testleri tekrar çalıştırın ve bir testin daha başarılı olduğundan emin olun.
10. Yorumları güncelleme: Zamana dayalı kurallar
Yorumların iş mantığı, yorum yazarı tarafından oluşturulduktan sonra bir saat boyunca düzenlenebilir olmasıdır. Bunu uygulamak için createdAt
zaman damgasını kullanın.
İlk olarak, kullanıcının yazar olduğunu doğrulamak için:
request.auth.uid == resource.data.authorUID
Ardından, yorum son bir saat içinde oluşturulur:
(request.time - resource.data.createdAt) < duration.value(1, 'h');
Bunları mantıksal AND operatörüyle birleştirdiğinizde yorum güncelleme kuralı şu şekilde olur:
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 tekrar çalıştırın ve bir testin daha başarılı olduğundan emin olun.
11. Yorumları silme: Üst sahiplikleri kontrol etme
Yorumlar; yorumun yazarı, moderatörü veya blog yayınının yazarı tarafından silinebilir.
İlk olarak, eklediğiniz yardımcı işlevi yayın 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 yayınının yazarı olup olmadığını kontrol etmek için get
kullanarak yayını Firestore'da 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 operatörü 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 tekrar çalıştırın ve bir testin daha başarılı olduğundan emin olun.
Kurallar dosyasının tamamı:
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. Sonraki adımlar
Tebrikler! Tüm testlerin başarılı olmasını sağlayan Güvenlik Kurallarını yazdınız ve uygulamanın güvenliğini sağladınız.
Bir sonraki adımda ele alabileceğiniz ilgili bazı konular aşağıda belirtilmiştir:
- Blog yayını: Güvenlik Kuralları'nın kod incelemesini yapma
- Codelab: Emülatörlerle ilk yerel geliştirme sürecini adım adım öğrenin
- Video: GitHub İşlemleri kullanılarak emülatör tabanlı testler için set CI'yı kullanma