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 parçalarından biridir. Ve bu codelab size bu konuda yol gösterecek.
Ö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 kullanın ; 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 codelab'de Firestore üzerine kurulu basit bir blog platformunu güvence altına alacaksınız. Güvenlik Kurallarına göre birim testleri çalıştırmak ve kuralların beklediğiniz erişime izin verip vermediğinden emin olmak için Firestore öykünücüsünü kullanacaksınız.
Nasıl yapılacağını öğreneceksiniz:
- Ayrıntılı izinler verme
- Verileri ve tür doğrulamalarını zorunlu kılın
- Öznitelik Tabanlı Erişim Denetimini 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. Uygulama işlevselliğinin üst düzey bir özetini burada bulabilirsiniz:
Taslak blog gönderileri:
- Kullanıcılar,
drafts
koleksiyonunda yer alan taslak blog gönderileri oluşturabilir. - Yazar, yayınlanmaya hazır olana kadar 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şturulabilir.
- Yalnızca geçici olarak silinebilirler; bu,
visible
bir özelliğ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 yorum bırakabilmeleri için doğrulanmış bir e-posta adresine sahip olmaları ve reddedilen grupta olmaması gerekir.
- Yorumlar yalnızca yayınlandıktan sonraki 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ı uygulayan Güvenlik Kuralları oluşturacaksınız.
Firebase Emulator Suite kullanılarak her şey yerel olarak gerçekleşecek.
Kaynak kodunu alın
Bu codelab'de, Güvenlik Kurallarına yönelik testlerle başlayacaksınız, ancak Güvenlik Kuralları minimum düzeyde olacaktır; 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 başlangıç durumu dizinine geçin:
$ cd codelab-rules/initial-state
Şimdi testleri çalıştırabilmek için bağımlılıkları yükleyin. 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 yüklenebilen 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 doğrulayın. Bu codelab'in 8.4.0 veya üzeri sürümlerle çalışması gerekir 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ın
Çalışacağınız uygulamanın üç ana Firestore koleksiyonu vardır: drafts
, devam eden 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 gereken kullanıcı özelliklerini ve diğer koşulları tanımlayan Güvenlik Kurallarına yönelik birim testleriyle birlikte gelir. Bu testlerin başarılı olabilmesi için Güvenlik Kurallarını yazacaksınız.
Başlangıç olarak veritabanınız kilitlenir: veritabanına okuma ve yazma işlemleri evrensel olarak reddedilir ve tüm testler başarısız olur. 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 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 ilerleyin:
$ 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 başarısızlık var. Kural dosyasını oluştururken daha fazla testin geçiş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 /drafts
adlı ayrı bir koleksiyonda saklar. Taslaklara yalnızca yazar veya moderatör tarafından erişilebilir ve gerekli ve değiştirilemez alanlar için doğrulamalar bulunur.
firestore.rules
dosyasını açtığınızda varsayılan bir kurallar dosyası bulacaksınız:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Match ifadesi, 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, şu anda aynı genel kural, isteği yapanın kim olduğuna veya hangi veriyi okumaya veya yazmaya çalıştığına bakılmaksızın tüm istekler için geçerlidir.
En içteki match ifadesini kaldırıp onu match /drafts/{draftID}
ile değiştirerek başlayın. (Belgelerin yapısına ilişkin yorumlar kurallar açısından yararlı 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 kimin oluşturabileceğini kontrol edecektir. Bu uygulamada taslaklar yalnızca yazar olarak belirtilen kişi tarafından oluşturulabilir. Talepte bulunan kişinin UID'sinin belgede listelenen UID ile aynı olup olmadığını kontrol edin.
Yaratmanı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çeriyorsa oluşturulabilir. (Kullanıcı createdAt
alanını sağlamaz; bu, uygulamanın bir belge oluşturmaya çalışmadan önce bu alanı eklemesini zorunlu kılar.) Yalnızca niteliklerin oluşturulduğunu kontrol etmeniz gerektiğinden, request.resource
tüm özelliklere sahip olup olmadığını kontrol edebilirsiniz. şu anahtarlar:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
Blog yazısı oluşturmanın 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ü &&
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 tekrar çalıştırın ve ilk testin geçtiğini doğrulayın.
5. Blog yazısı taslaklarını güncelleyin.
Daha sonra yazarlar taslak blog gönderilerini hassaslaştırdıkça taslak belgeleri düzenleyeceklerdir. Bir gönderinin güncellenebileceği koşullar için bir kural oluşturun. Öncelikle taslaklarını yalnızca yazar güncelleyebilir. Burada önceden yazılmış olan UID'yi resource.data.authorUID
kontrol ettiğinizi unutmayın:
resource.data.authorUID == request.auth.uid
Güncellemenin ikinci gereksinimi, authorUID
ve createdAt
adlı iki özelliğin değişmemesidir:
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]);
Son olarak başlık 50 karakter veya daha az 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;
Kuralların tamamı şö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 doğrulayın.
6. Taslakları silin ve okuyun: Nitelik Tabanlı Erişim Kontrolü
Yazarlar taslak oluşturup güncelleyebildiği gibi taslakları da silebilir.
resource.data.authorUID == request.auth.uid
Ayrıca, kimlik doğrulama belirteçlerinde isModerator
niteliğine sahip yazarların taslakları silmelerine izin verilir:
request.auth.token.isModerator == true
Bu koşullardan herhangi biri silme işlemi için yeterli olduğundan, bunları mantıksal OR operatörü ||
ile birleştirin. :
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
Artık kuralların tamamı şöyle:
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 geçtiğini doğrulayın.
7. Yayınlanan gönderileri okur, oluşturur ve siler: farklı erişim kalıpları için normalleştirme
Yayınlanan gönderiler ve taslak gönderiler için erişim modelleri çok farklı olduğundan, bu uygulama gönderileri ayrı draft
ve published
koleksiyonlar halinde normalleştirmez. Örneğin, yayınlanan gönderiler 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 yazısı yayınlamak istediğinde, yeni yayınlanan yazıyı oluşturacak bir işlev tetiklenir.
Daha sonra yayınlanan gönderilere ilişkin kuralları yazacaksınız. Yazılması gereken en basit kural, yayınlanan gönderilerin herkes tarafından okunabilmesi ve hiç kimse tarafından oluşturulamaması veya 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ğimizde 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 yeniden çalıştırın ve başka bir testin geçtiğini doğrulayın.
8. Yayınlanan gönderileri güncelleme: Özel işlevler ve yerel değişkenler
Yayınlanan 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 olmanın koşullarını zaten yazmış olduğunuzdan, koşulları kopyalayıp yapıştırabilirsiniz, ancak zamanla bunların okunması ve bakımı zorlaşabilir. Bunun yerine, yazar veya moderatör olmanın mantığını özetleyen özel bir işlev oluşturacaksınız. Daha sonra bunu birden fazla koşuldan arayacaksınız.
Özel bir işlev oluşturun
Taslaklar için match ifadesinin üzerinde, argüman olarak bir gönderi belgesini (bu, taslaklar veya yayınlanmış gönderiler için işe yarayacaktır) ve kullanıcının kimlik doğrulama nesnesini alan isAuthorOrModerator
adında 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 return ifadesiyle bitmelidir ve bizimki, değişkenlerden birinin doğru olup olmadığını belirten bir boole değeri döndürecektir:
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
Fonksiyonu çağırın
Şimdi, ilk argüman olarak resource.data
aktarmaya dikkat ederek, taslaklara ilişkin kuralı bu işlevi çağıracak şekilde 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ınlanmış gönderileri güncellemek için yeni işlevi de kullanan bir koşul yazabilirsiniz:
allow update: if isAuthorOrModerator(resource.data, request.auth);
Doğrulama ekle
Yayınlanan bir gönderinin bazı alanları değiştirilmemelidir; özellikle url
, authorUID
publishedAt
alanları değiştirilemez. Diğer iki alan ( title
, content
ve visible
) güncellemeden sonra da mevcut olmalıdır. Yayınlanan gönderilerdeki güncellemeler için bu gereksinimlerin uygulanmasını sağlayacak 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
adında yeni bir işlev oluşturarak yapabilirsiniz. Yeni işlevle birlikte, yayınlanan bir gönderinin güncellenmesinin koşulu şöyle 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);
Ve kural 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 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 5 geçme testiniz ve 4 başarısız testiniz olmalıdır.
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 ( /published/{postID}/comments/{commentID}
) saklanır. Varsayılan olarak bir koleksiyonun kuralları alt koleksiyonlara uygulanmaz. Yayınlanan gönderinin ana belgesi için geçerli olan kuralların aynılarının yorumlara da uygulanmasını istemezsiniz; farklı olanları yaratacaksınız.
Yorumlara erişim kurallarını yazmak için match 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: İsimsiz olamaz
Bu uygulama için, yorumları anonim bir hesap değil, yalnızca kalıcı bir hesap oluşturmuş olan kullanıcılar okuyabilir. Bu kuralı uygulamak için her auth.token
nesnesinde sign_in_provider
niteliğ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 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 adresi olması gerekir
- yorum 500 karakterden az olmalı ve
- firestore'da
bannedUsers
koleksiyonunda saklanan 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şturmanın 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));
Kurallar dosyasının tamamı artık şu şekildedir:
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 yeniden çalıştırın ve bir testin daha geçtiğinden emin olun.
10. Yorumların güncellenmesi: Zamana dayalı kurallar
Yorumların iş mantığı, yorum yazarı tarafından oluşturulduktan sonra bir saat boyunca düzenlenebilmesidir. Bunu uygulamak için createdAt
zaman damgasını kullanın.
Öncelikle kullanıcının yazar olduğunu tespit etmek için:
request.auth.uid == resource.data.authorUID
Daha sonra, 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ştirdiğimizde yorumları güncelleme kuralı şöyle 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 yeniden çalıştırın ve bir testin daha geçtiğinden emin olun.
11. Yorumların silinmesi: Ebeveyn sahipliğinin kontrol edilmesi
Yorumlar, yorum yazarı, moderatör veya blog yazısının 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 Firestore'da gönderiyi aramak üzere bir get
kullanın:
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID
Bu koşullardan herhangi biri yeterli olduğundan, bunların arasında mantıksal OR operatörünü 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 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 geçmesini sağlayan ve uygulamanın güvenliğini sağlayan Güvenlik Kurallarını yazdınız!
Aşağıda daha sonra incelenecek ilgili bazı konular verilmiştir:
- Blog yazısı : Güvenlik Kuralları nasıl gözden geçirilir?
- Codelab : Emülatörlerle 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?