Kullanıcılar ve gruplar için güvenli veri erişimi

Birçok ortak çalışma uygulaması, kullanıcıların belirli izinlere bağlı olarak farklı veriler okumasına ve yazmasına olanak tanır. Örneğin, bir doküman düzenleme uygulamasında kullanıcılar hem birkaç kullanıcının dokümanlarını okumasına ve yazmasına izin vermek hem de istenmeyen erişimi engellemek 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 gereksinimleriyle "hikaye" ve "yorum" oluşturabileceği, ortak çalışmaya dayalı bir yazma uygulaması oluşturduğunuzu 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, okuyucuların tüm erişimine sahiptir ve haberlere yorum ekleyebilirler.
  • Yazarlar, yorumcuların tüm erişimine sahiptir ve hikaye içeriklerini 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, her belgenin söz konusu habere dair bir yorum olduğu comments alt koleksiyonu vardır.

Erişim rollerini takip etmek için kullanıcı kimliklerini rollerle eşleyen bir roles alanı ekleyin:

/Hikayeler/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

Yorumlarda, yazarın kullanıcı kimliği ve bazı içerikler olmak üzere yalnızca iki alan bulunur:

/Hikayeler/{storyid}/yorumlar/{commentid}

{
  user: "alice",
  content: "I think this is a great story!"
}

Kurallar

Artık kullanıcı rollerinin veritabanına kaydedildiğine göre bunları doğrulamak için Güvenlik Kuralları yazmanız gerekir. Bu kurallarda uygulamanın Firebase Auth kullandığı varsayılır. Böylece 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 kural 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ı rollerinin ve yeni dokümanların 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 bir kullanıcının hikayeleri ve yorumları okumasına izin veren kurallar yazın. Önceki adımda tanımlanan işlevlerin kullanılması kuralların kısa ve okunabilir olmasını sağlar:

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ı, yorumcuları ve sahiplerinin yorum yayınlamasına izin verin. Bu kuralın, yorumun owner değerinin istekte bulunan kullanıcıyla eşleştiğini de doğruladığını ve böylece kullanıcıların birbirlerinin yorumlarını yazmalarını engellediğini unutmayın:

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 olanağı verin ancak hikaye rollerini düzenleme veya belgenin diğer özelliklerini değiştirme izni vermeyin. Yazarlar yalnızca hikayeleri güncelleyebildiğinden bunun için hikaye write 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üm, Güvenlik Kuralları kullanılarak kullanıcı verilerinin güvenliğinin sağlanmasını gösterse de aşağıdaki sınırlamalara dikkat etmeniz gerekir:

  • Ayrıntı düzeyi: Yukarıdaki örnekte birden fazla rolün (yazar ve sahip) aynı belgeye farklı sınırlamalarıyla yazma erişimi vardır. Daha karmaşık belgelerle bu özelliği yönetmek zor olabilir. Ayrıca, tek bir dokümanı her biri tek bir role sahip birden fazla dokümana bölmek daha iyi bir yaklaşım olabilir.
  • Büyük Gruplar: Çok büyük veya karmaşık gruplarla paylaşımda bulunmanız gerekiyorsa rollerin hedef belgedeki bir alan olarak değil, kendi koleksiyonlarında depolandığı bir sistemi tercih edin.