Birçok ortak çalışma uygulaması, kullanıcıların bir dizi izne bağlı olarak farklı veri parçalarını okumasına ve yazmasına olanak tanır. Örneğin, bir doküman düzenleme uygulamasında kullanıcılar, istenmeyen erişimi engellerken birkaç kullanıcının dokümanlarını okumasına ve yazmalarına izin vermek isteyebilir.
Çözüm: Rol Tabanlı Erişim Denetimi
Uygulamanızda rol tabanlı erişim denetimi uygulamak için Cloud Firestore'un veri modelinin yanı sıra özel güvenlik kurallarından yararlanabilirsiniz.
Kullanıcıların aşağıdaki güvenlik şartlarına uygun şekilde "hikayeler" ve "yorumlar" oluşturabileceği bir ortak yazma uygulaması oluşturduğunuzu varsayalım:
- Her hikayenin bir sahibi vardır ve "yazarlarla", "yorumcularla" ve "okuyucularla" paylaşılabilir.
- Okuyucular yalnızca hikayeleri ve yorumları görebilir. Hiçbir öğeyi düzenleyemezler.
- Yorumcular, okuyucuların sahip olduğu tüm özelliklere erişebilir ve hikayelere yorum ekleyebilir.
- Yazarlar, yorumcuların sahip olduğu tüm erişime sahiptir ve hikaye içeriğini de düzenleyebilir.
- Sahipler, hikayenin herhangi bir bölümünü düzenleyebilir ve diğer kullanıcıların erişimini kontrol edebilir.
Veri Yapısı
Uygulamanızda her dokümanın bir hikayeyi temsil ettiği bir stories
koleksiyonu olduğunu varsayalım. Her hikayenin ayrıca, her belgenin ilgili hikayeyle ilgili bir yorum olduğu bir comments
alt koleksiyonu da vardır.
Erişim rollerini takip etmek için kullanıcı kimliklerinin rollerle eşlemesi olan bir roles
alanı ekleyin:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time ...",
roles: {
alice: "owner",
bob: "reader",
david: "writer",
jane: "commenter"
// ...
}
}
Yorumlar yalnızca iki alan içerir: yazarı tanımlayan kullanıcı kimliği ve bazı içerikler:
/stories/{storyid}/comments/{commentid}
{
user: "alice",
content: "I think this is a great story!"
}
Kurallar
Kullanıcıların rollerini veritabanına kaydettiğiniz için bunları doğrulamak üzere Güvenlik Kuralları yazmanız gerekir. Bu kurallar, uygulamanın Firebase Auth kullandığı varsayılarak yazılmıştır. Bu durumda request.auth.uid
değişkeni kullanıcının kimliği olur.
1. Adım: Hikayeler ve yorumlar için boş kurallar içeren temel bir kurallar dosyasıyla başlayın:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{story} {
// TODO: Story rules go here...
match /comments/{comment} {
// TODO: Comment rules go here...
}
}
}
}
2. adım: Sahiplerin hikayeler üzerinde tam kontrol sahibi olmasını sağlayan basit bir write
kuralı ekleyin. Tanımlanan işlevler, kullanıcının rollerini ve yeni belgelerin geçerli olup olmadığını belirlemeye yardımcı olur:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{story} {
function isSignedIn() {
return request.auth != null;
}
function getRole(rsc) {
// Read from the "roles" map in the resource (rsc).
return rsc.data.roles[request.auth.uid];
}
function isOneOfRoles(rsc, array) {
// Determine if the user is one of an array of roles
return isSignedIn() && (getRole(rsc) in array);
}
function isValidNewStory() {
// Valid if story does not exist and the new story has the correct owner.
return resource == null && isOneOfRoles(request.resource, ['owner']);
}
// Owners can read, write, and delete stories
allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);
match /comments/{comment} {
// ...
}
}
}
}
3. Adım: Herhangi bir role sahip kullanıcıların hikayeleri ve yorumları okumasına izin veren kurallar yazın. Önceki adımda tanımlanan işlevleri kullanarak kuralları kısa ve okunaklı hale getirebilirsiniz:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{story} {
function isSignedIn() {
return request.auth != null;
}
function getRole(rsc) {
return rsc.data.roles[request.auth.uid];
}
function isOneOfRoles(rsc, array) {
return isSignedIn() && (getRole(rsc) in array);
}
function isValidNewStory() {
return resource == null
&& request.resource.data.roles[request.auth.uid] == 'owner';
}
allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);
// Any role can read stories.
allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);
match /comments/{comment} {
// Any role can read comments.
allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter', 'reader']);
}
}
}
}
4. Adım: Hikaye yazarlarının, yorumcuların ve sahiplerinin yorum yayınlamasına izin verin.
Bu kuralın, yorumun owner
değerinin istek gönderen kullanıcıyla eşleşip eşleşmediğini de doğruladığını unutmayın. Bu sayede kullanıcılar birbirlerinin yorumlarının üzerine yazı yazamaz:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{story} {
function isSignedIn() {
return request.auth != null;
}
function getRole(rsc) {
return rsc.data.roles[request.auth.uid];
}
function isOneOfRoles(rsc, array) {
return isSignedIn() && (getRole(rsc) in array);
}
function isValidNewStory() {
return resource == null
&& request.resource.data.roles[request.auth.uid] == 'owner';
}
allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);
match /comments/{comment} {
allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter', 'reader']);
// Owners, writers, and commenters can create comments. The
// user id in the comment document must match the requesting
// user's id.
//
// Note: we have to use get() here to retrieve the story
// document so that we can check the user's role.
allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter'])
&& request.resource.data.user == request.auth.uid;
}
}
}
}
5. adım: Yazarlara hikaye içeriğini düzenleme izni verin ancak hikaye rollerini düzenlemelerine veya belgenin diğer özelliklerini değiştirmelerine izin vermeyin. Yazarlar yalnızca hikayeleri güncelleyebildiğinden, hikayeler write
kuralı create
, update
ve delete
için ayrı kurallara bölünmelidir:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{story} {
function isSignedIn() {
return request.auth != null;
}
function getRole(rsc) {
return rsc.data.roles[request.auth.uid];
}
function isOneOfRoles(rsc, array) {
return isSignedIn() && (getRole(rsc) in array);
}
function isValidNewStory() {
return request.resource.data.roles[request.auth.uid] == 'owner';
}
function onlyContentChanged() {
// Ensure that title and roles are unchanged and that no new
// fields are added to the document.
return request.resource.data.title == resource.data.title
&& request.resource.data.roles == resource.data.roles
&& request.resource.data.keys() == resource.data.keys();
}
// Split writing into creation, deletion, and updating. Only an
// owner can create or delete a story but a writer can update
// story content.
allow create: if isValidNewStory();
allow delete: if isOneOfRoles(resource, ['owner']);
allow update: if isOneOfRoles(resource, ['owner'])
|| (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);
match /comments/{comment} {
allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter', 'reader']);
allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
['owner', 'writer', 'commenter'])
&& request.resource.data.user == request.auth.uid;
}
}
}
}
Sınırlamalar
Yukarıda gösterilen çözümde, Güvenlik Kuralları kullanılarak kullanıcı verilerinin güvenliğinin nasıl sağlandığı gösterilmektedir. Ancak aşağıdaki sınırlamalara dikkat etmeniz gerekir:
- Ayrıntı düzeyi: Yukarıdaki örnekte, birden fazla rolün (yazar ve sahip) aynı dokümana yazma erişimi vardır ancak bu erişim farklı sınırlamalara tabidir. Bu, daha karmaşık dokümanlarda yönetilmesi zor olabilir. Bu nedenle, tek dokümanları her biri tek bir role ait olacak şekilde birden fazla dokümana bölmek daha iyi olabilir.
- Büyük Gruplar: Çok büyük veya karmaşık gruplarla paylaşım yapmanız gerekiyorsa rollerin hedef dokümanda bir alan olarak değil, kendi koleksiyonlarında depolandığı bir sistem kullanabilirsiniz.