Birçok ortak çalışma uygulaması, kullanıcıların bir dizi izne bağlı olarak farklı veri parçalarını okuyup 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ı okuyup yazmasına izin vermek isteyebilir.
Çözüm: Rol tabanlı erişim denetimi
Uygulamanızda role dayalı erişim kontrolü uygulamak için Cloud Firestore'un veri modelinin yanı sıra özel güvenlik kurallarından da yararlanabilirsiniz.
Kullanıcıların aşağıdaki güvenlik şartlarına uygun olarak "hikayeler" ve "yorumlar" oluşturabileceği ortak yazma uygulaması geliştirdiğinizi varsayalım:
- Her hikayenin bir sahibi vardır ve "yazarlar", "yorumcular" ve "okuyucular" ile paylaşılabilir.
- Okuyucular yalnızca hikayeleri ve yorumları görebilir. Hiçbir öğeyi düzenleyemezler.
- Yorumcular, okuyucularla aynı erişim haklarına sahiptir ve hikayelere yorum ekleyebilir.
- Yazarlar, yorumcuların tüm erişim haklarına 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 bir de comments
alt koleksiyonu vardır. Bu alt koleksiyondaki her doküman, ilgili hikayeyle ilgili bir yorumdur.
Erişim rollerini takip etmek için roles
alanı ekleyin. Bu alan, kullanıcı kimliklerinin rollerle eşlendiği bir haritadır:
/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ın 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ı rollerini veritabanına kaydettiğinize göre, bunları doğrulamak için güvenlik kuralları yazmanız gerekir. Bu kurallar, uygulamanın request.auth.uid
değişkeninin kullanıcının kimliği olması için Firebase Auth kullandığını varsayar.
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: Sahiplere hikayeler üzerinde tam kontrol 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ının hikayeleri ve yorumları okumasına izin veren kurallar yazın. Önceki adımda tanımlanan işlevleri kullanmak kuralları kısa ve okunabilir tutar:
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ına, yorumculara ve sahiplerine yorum gönderme izni verin.
Bu kuralın, yorumun owner
değerinin istekte bulunan kullanıcıyla eşleştiğini de doğruladığını unutmayın. Bu sayede kullanıcılar birbirlerinin yorumlarını geçersiz kılamaz:
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 makale içeriğini düzenleme izni verin ancak makale rollerini düzenleme veya belgenin diğer özelliklerini değiştirme izni vermeyin. Yazarlar yalnızca hikayeleri güncelleyebildiğinden, write
hikayeleri bölme kuralının create
, update
ve delete
için ayrı kurallara bölünmesi gerekir:
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 hale getirilmesi gösterilmektedir. Ancak aşağıdaki sınırlamaları göz önünde bulundurmanız gerekir:
- Ayrıntı düzeyi: Yukarıdaki örnekte, aynı dokümana birden fazla rol (yazar ve sahip) yazma erişimine sahip ancak farklı sınırlamalar var. Bu durum, daha karmaşık dokümanlarda yönetilmesi zor bir hale gelebilir. Bu nedenle, tek bir dokümanı 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ümandaki bir alan olarak değil, kendi koleksiyonlarında depolandığı bir sistemi kullanabilirsiniz.