Verileri güvenli bir şekilde sorgulama

Bu sayfada, Cloud Firestore Security Rules'in sorgularla nasıl etkileşimde bulunduğu açıklanmaktadır. Bu açıklama için Güvenlik Kurallarını Yapılandırma ve Güvenlik Kuralları İçin Yazma Koşulları başlıklı makalelerdeki kavramlardan yararlanılmıştır. Bu makalede, güvenlik kurallarının yazabildiğiniz sorguları nasıl etkilediği daha ayrıntılı bir şekilde ele alınmakta ve sorgularınızın güvenlik kurallarınızla aynı kısıtlamaları kullandığından nasıl emin olabileceğiniz açıklanmaktadır. Bu sayfada, limit ve orderBy gibi sorgu özelliklerine göre sorgulara izin vermek veya sorguları reddetmek için güvenlik kurallarının nasıl yazılacağı da açıklanmaktadır.

Kurallar filtre değildir

Belgeleri almak için sorgu yazarken güvenlik kurallarının filtre olmadığını, sorguların "ya hep ya hiç" olduğunu unutmayın. Cloud Firestore, size zaman ve kaynak tasarrufu sağlamak için bir sorguyu tüm dokümanlarınızın gerçek alan değerleri yerine potansiyel sonuç kümesine göre değerlendirir. Bir sorgu, istemcinin okuma izni olmayan belgeleri döndürebilecekse isteğin tamamı başarısız olur.

Sorgular ve güvenlik kuralları

Aşağıdaki örneklerde gösterildiği gibi, sorgularınızı güvenlik kurallarınızın kısıtlamalarına uygun olacak şekilde yazmanız gerekir.

auth.uid'ye göre dokümanları güvenli hale getirme ve sorgulama

Aşağıdaki örnekte, bir güvenlik kuralı tarafından korunan dokümanları almak için nasıl sorgu yazılacağı gösterilmektedir. story doküman içeren bir veritabanı düşünün:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

Her doküman, title ve content alanlarına ek olarak erişim denetimi için kullanılacak author ve published alanlarını depolar. Bu örneklerde, uygulamanın author alanını belgeyi oluşturan kullanıcının UID'sine ayarlamak için Firebase Authentication kullandığı varsayılmaktadır. Firebase Authentication, güvenlik kurallarındaki request.auth değişkenini de doldurur.

Aşağıdaki güvenlik kuralı, her story için yazarına ait okuma ve yazma erişimini kısıtlamak amacıyla request.auth ve resource.data değişkenlerini kullanır:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Uygulamanızda, kullanıcıya yazdığı storydokümanların listesini gösteren bir sayfanın bulunduğunu varsayalım. Bu sayfayı doldurmak için aşağıdaki sorguyu kullanabileceğinizi düşünebilirsiniz. Ancak bu sorgu, güvenlik kurallarınızla aynı kısıtlamaları içermediği için başarısız olur:

Geçersiz: Sorgu kısıtlamaları, güvenlik kuralları kısıtlamalarıyla eşleşmiyor

// This query will fail
db.collection("stories").get()

Geçerli kullanıcı her story belgesinin yazarı olsa bile sorgu başarısız olur. Bu davranışın nedeni, Cloud Firestore güvenlik kurallarınızı uygularken sorguyu veritabanınızdaki dokümanların gerçek özelliklerine göre değil, potansiyel sonuç kümesine göre değerlendirmesidir. Bir sorgu, güvenlik kurallarınızı ihlal eden dokümanları potansiyel olarak içeriyorsa sorgu başarısız olur.

Buna karşılık, aşağıdaki sorgu, author alanında güvenlik kurallarıyla aynı kısıtlamayı içerdiği için başarılı olur:

Geçerli: Sorgu kısıtlamaları, güvenlik kuralları kısıtlamalarıyla eşleşir.

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Bir alana göre dokümanları güvenli hale getirme ve sorgulama

Sorgular ile kurallar arasındaki etkileşimi daha iyi göstermek için aşağıdaki güvenlik kuralları, published alanının true olarak ayarlandığı story dokümanlarını herhangi bir kullanıcının okumasına izin verecek şekilde stories koleksiyonunun okuma erişimini genişletir.

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Yayınlanan sayfalar için sorgu, güvenlik kurallarıyla aynı kısıtlamaları içermelidir:

db.collection("stories").where("published", "==", true).get()

.where("published", "==", true) sorgu kısıtlaması, resource.data.published değerinin herhangi bir sonuç için true olduğunu garanti eder. Bu nedenle, bu sorgu güvenlik kurallarını karşılar ve verileri okumasına izin verilir.

OR sorgu

Cloud Firestore, mantıksal bir OR sorgusunu (or, in veya array-contains-any) bir kural kümesine göre değerlendirirken her karşılaştırma değerini ayrı ayrı değerlendirir. Her karşılaştırma değeri, güvenlik kuralı kısıtlamalarını karşılamalıdır. Örneğin, aşağıdaki kural için:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

Geçersiz: Sorgu, tüm olası dokümanlar için x > 5 değerini garanti etmez

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

Geçerli: Sorgu, tüm olası belgeler için x > 5 değerini garanti eder.

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

Sorgulardaki kısıtlamaları değerlendirme

Güvenlik kurallarınız, sorguları kısıtlamalarına göre kabul edebilir veya reddedebilir. request.query değişkeni, bir sorgunun limit, offset ve orderBy özelliklerini içerir. Örneğin, güvenlik kurallarınız, döndürülen maksimum belge sayısını belirli bir aralıkla sınırlamayan tüm sorguları reddedebilir:

allow list: if request.query.limit <= 10;

Aşağıdaki kural kümesinde, sorgulara uygulanan kısıtlamaları değerlendiren güvenlik kurallarının nasıl yazılacağı gösterilmektedir. Bu örnekte, önceki stories kural kümesi aşağıdaki değişikliklerle genişletilmiştir:

  • Kural kümesi, okuma kuralını get ve list kurallarına ayırır.
  • get kuralı, tek dokümanların alınmasını herkese açık dokümanlar veya kullanıcının yazdığı dokümanlarla sınırlandırır.
  • list kuralı, get ile aynı kısıtlamaları sorgularda uygular. Sorgu sınırını da kontrol eder ve ardından sınırı olmayan veya 10'dan büyük sınıra sahip tüm sorguları reddeder.
  • Kural kümesi, kod yinelemesini önlemek için bir authorOrPublished() işlevi tanımlar.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Koleksiyon grubu sorguları ve güvenlik kuralları

Varsayılan olarak sorgular tek bir koleksiyonla sınırlıdır ve yalnızca bu koleksiyondan sonuç alır. Koleksiyon grubu sorguları sayesinde, aynı kimliğe sahip tüm koleksiyonlardan oluşan bir koleksiyon grubundan sonuç alabilirsiniz. Bu bölümde, güvenlik kurallarını kullanarak koleksiyon grubu sorgularınızı nasıl güvence altına alacağınız açıklanmaktadır.

Koleksiyon gruplarına göre dokümanları güvenli hale getirme ve sorgulama

Güvenlik kurallarınızda, toplama grubu için bir kural yazarak toplama grubu sorgularına açıkça izin vermeniz gerekir:

  1. rules_version = '2'; kural kümenizin ilk satırı olmalıdır. Koleksiyon grubu sorguları, güvenlik kuralları 2 sürümünün yeniden kullanılabilir {name=**} özelliğini gerektirir.
  2. match /{path=**}/[COLLECTION_ID]/{doc} kullanarak koleksiyon grubunuz için bir kural yazın.

Örneğin, posts alt koleksiyon içeren forum belgeye ayrılmış bir forumu ele alalım:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

Bu uygulamada, gönderileri sahipleri tarafından düzenlenebilir ve kimliği doğrulanmış kullanıcılar tarafından okunabilir hale getiriyoruz:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Kimliği doğrulanmış tüm kullanıcılar, herhangi bir forumdaki yayınları alabilir:

db.collection("forums/technology/posts").get()

Ancak mevcut kullanıcıya tüm forumlardaki yayınlarını göstermek isterseniz ne olur? Tüm posts koleksiyonlarından sonuç almak için koleksiyon grubu sorgusu kullanabilirsiniz:

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

Güvenlik kurallarınızda, posts koleksiyon grubu için bir okuma veya listeleme kuralı yazarak bu sorguya izin vermeniz gerekir:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

Ancak bu kuralların, hiyerarşiden bağımsız olarak posts kimliğine sahip tüm koleksiyonlar için geçerli olacağını unutmayın. Örneğin, bu kurallar aşağıdaki koleksiyonların tümü için geçerlidir: posts

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Bir alana göre güvenli koleksiyon grubu sorguları

Tek koleksiyon sorguları gibi koleksiyon grubu sorguları da güvenlik kurallarınız tarafından belirlenen kısıtlamaları karşılamalıdır. Örneğin, yukarıdaki stories örneğinde yaptığımız gibi her forum yayınına bir published alanını ekleyebiliriz:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Ardından, published durumuna ve author yayınına göre posts koleksiyon grubu için kurallar yazabiliriz:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Bu kurallarla web, Apple ve Android istemcileri aşağıdaki sorguları yapabilir:

  • Herkes bir forumda yayınlanan yayınları alabilir:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Herkes, bir yazarın tüm forumlarda yayınlanan yayınlarını alabilir:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Yazarlar, tüm forumlardaki yayınlanmış ve yayınlanmamış tüm yayınlarını alabilir:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Koleksiyon grubuna ve doküman yoluna göre dokümanları koruma ve sorgulama

Bazı durumlarda, koleksiyon grubu sorgularını doküman yoluna göre kısıtlamak isteyebilirsiniz. Bu kısıtlamaları oluşturmak için, dokümanları bir alana göre korumak ve sorgulamak için kullandığınız teknikleri kullanabilirsiniz.

Her kullanıcının çeşitli borsa ve kripto para borsalarındaki işlemlerini takip eden bir uygulamayı düşünün:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

user alanına dikkat edin. transaction belgesinin sahibini, belgenin yolundan bilsek bile bu bilgiyi her transaction belgesinde kopyalıyoruz. Bu sayede iki şey yapabiliriz:

  • Belge yollarında belirli bir /users/{userid} içeren dokümanlarla sınırlı koleksiyon grubu sorguları yazın. Örneğin:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Bir kullanıcının başka bir kullanıcının transaction dokümanlarını almaması için transactions koleksiyon grubundaki tüm sorgular için bu kısıtlamayı uygulayın.

Bu kısıtlamayı güvenlik kurallarımızda uygular ve user alanı için veri doğrulama özelliğini ekleriz:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

Sonraki adımlar