Belirli alanlara erişimi kontrol etme

Bu sayfada, Cloud Firestore Security Rules'i kullanarak istemcilerin bir dokümandaki bazı alanlar üzerinde işlem yapmasına izin veren, diğer alanlar üzerinde işlem yapmasına izin vermeyen kurallar oluşturma hakkında bilgi verilmektedir. Bu bilgiler, Güvenlik Kurallarını Yapılandırma ve Güvenlik Kuralları İçin Koşul Yazma başlıklı makalelerdeki kavramları temel alır.

Bir dokümanda yapılan değişiklikleri doküman düzeyinde değil, alan düzeyinde kontrol etmek isteyebilirsiniz.

Örneğin, bir müşterinin doküman oluşturmasına veya değiştirmesine izin vermek ancak söz konusu dokümandaki belirli alanları düzenlemesine izin vermemek isteyebilirsiniz. Alternatif olarak, bir müşterinin oluşturduğu tüm dokümanların her zaman belirli bir alan grubu içermesini zorunlu kılmak isteyebilirsiniz. Bu kılavuzda, Cloud Firestore Security Rules'ü kullanarak bu görevlerin bazılarını nasıl tamamlayabileceğiniz açıklanmaktadır.

Yalnızca belirli alanlar için okuma erişimine izin verme

Cloud Firestore'te okuma işlemleri doküman düzeyinde gerçekleştirilir. Belgenin tamamını alırsınız veya hiçbir şey almazsınız. Kısmi doküman alınamaz. Kullanıcıların bir doküman içindeki belirli alanları okumasını engellemek için yalnızca güvenlik kurallarını kullanmak mümkün değildir.

Bir dokümanda bazı kullanıcılardan gizlemek istediğiniz belirli alanlar varsa en iyi yöntem bunları ayrı bir dokümana yerleştirmektir. Örneğin, private alt koleksiyonunda aşağıdaki gibi bir doküman oluşturabilirsiniz:

/calisanlar/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

Ardından, iki koleksiyon için farklı erişim düzeylerine sahip güvenlik kuralları ekleyebilirsiniz. Bu örnekte, yalnızca Finance değerine eşit role özel kimlik doğrulama iddiasına sahip kullanıcıların bir çalışanın finansal bilgilerini görüntüleyebileceğini belirtmek için özel kimlik doğrulama iddialarını kullanıyoruz.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

Doküman oluşturma sırasında alanları kısıtlama

Cloud Firestore şemasızdır, yani veritabanı düzeyinde bir dokümanın hangi alanları içerdiğine dair herhangi bir kısıtlama yoktur. Bu esneklik, geliştirmeyi kolaylaştırabilir. Ancak müşterilerin yalnızca belirli alanları içeren veya başka alanların bulunmadığı belgeler oluşturabildiğinden emin olmak istediğiniz zamanlar olabilir.

Bu kuralları, request.resource.data nesnesinin keys yöntemini inceleyerek oluşturabilirsiniz. Bu, istemcinin bu yeni belgeye yazmaya çalıştığı tüm alanların listesidir. Bu alan grubunu hasOnly() veya hasAny() gibi işlevlerle birleştirerek, kullanıcının Cloud Firestore'a ekleyebileceği belge türlerini kısıtlayan bir mantık ekleyebilirsiniz.

Yeni dokümanlarda belirli alanları zorunlu kılma

Bir restaurant koleksiyonunda oluşturulan tüm dokümanların en az name, location ve city alanı içerdiğinden emin olmak istediğinizi varsayalım. Bunu yeni dokümandaki anahtar listesinde hasAll() öğesini çağırarak yapabilirsiniz.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

Bu, restoranların diğer alanlarla da oluşturulmasına olanak tanır ancak bir müşteri tarafından oluşturulan tüm dokümanların en az bu üç alanı içermesini sağlar.

Yeni dokümanlarda belirli alanları yasaklama

Benzer şekilde, yasaklanmış alanların listesine karşı hasAny() kullanarak istemcilerin belirli alanları içeren dokümanlar oluşturmasını engelleyebilirsiniz. Bir belge bu alanlardan herhangi birini içeriyorsa bu yöntem doğru olarak değerlendirilir. Bu nedenle, belirli alanları yasaklamak için sonucu reddetmek isteyebilirsiniz.

Örneğin, aşağıdaki örnekte bu alanlar daha sonra bir sunucu çağrısı tarafından ekleneceğinden, istemcilerin average_score veya rating_count alanı içeren bir doküman oluşturmasına izin verilmez.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

Yeni belgeler için izin verilen alanlar listesi oluşturma

Yeni belgelerde belirli alanları yasaklamak yerine, yalnızca yeni belgelerde açıkça izin verilen alanların listesini oluşturabilirsiniz. Ardından, oluşturulan yeni dokümanların yalnızca bu alanları (veya bu alanların bir alt kümesini) içerdiğinden emin olmak için hasOnly() işlevini kullanabilirsiniz.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Zorunlu ve isteğe bağlı alanları birleştirme

Bazı alanları zorunlu kılmak ve diğerlerine izin vermek için güvenlik kurallarınızda hasAll ve hasOnly işlemlerini birlikte kullanabilirsiniz. Örneğin, bu örnekte tüm yeni dokümanların name, location ve city alanlarını içermesi zorunludur ve isteğe bağlı olarak address, hours ve cuisine alanlarına izin verilir.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Gerçek bir senaryoda, kodunuzun kopyalanmasını önlemek ve isteğe bağlı ile zorunlu alanları tek bir listede daha kolay birleştirmek için bu mantığı bir yardımcı işleve taşımak isteyebilirsiniz. Örneğin:

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

Güncelleme sırasında alanlar kısıtlanıyor

Yaygın olarak kullanılan bir güvenlik uygulaması, istemcilerin yalnızca bazı alanları düzenlemesine izin vermek olup diğerlerini düzenlememektir. Bu işlemi yalnızca önceki bölümde açıklanan request.resource.data.keys() listesine bakarak yapamazsınız. Bu liste, güncellemeden sonra dokümanın tümünü temsil ettiğinden ve bu nedenle istemcinin değiştirmediği alanları içerdiğinden.

Ancak diff() işlevini kullanırsanız request.resource.data öğesini, güncellemeden önce veritabanındaki dokümanı temsil eden resource.data nesnesi ile karşılaştırabilirsiniz. Bu işlem, iki farklı harita arasındaki tüm değişiklikleri içeren bir mapDiff nesnesi oluşturur.

Bu mapDiff üzerinde affectedKeys() yöntemini çağırarak bir düzenlemede değiştirilen bir dizi alan elde edebilirsiniz. Ardından, bu grubun belirli öğeleri içerip içermediğinden emin olmak için hasOnly() veya hasAny() gibi işlevleri kullanabilirsiniz.

Bazı alanların değiştirilmesini önleme

affectedKeys() tarafından oluşturulan kümede hasAny() yöntemini kullanıp ardından sonucu olumsuzlayarak, değiştirilmesini istemediğiniz alanları değiştirmeye çalışan tüm istemci isteklerini reddedebilirsiniz.

Örneğin, müşterilerin bir restoranla ilgili bilgileri güncellemesine izin vermek ancak ortalama puanını veya yorum sayısını değiştirmelerine izin vermemek isteyebilirsiniz.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

Yalnızca belirli alanların değiştirilmesine izin verme

Değiştirilmesini istemediğiniz alanları belirtmek yerine hasOnly() işlevini kullanarak değiştirilmesini istediğiniz alanların listesini belirtebilirsiniz. Güvenlik kurallarınızda açıkça izin vermediğiniz sürece yeni belge alanlarına yazma işlemine varsayılan olarak izin verilmediğinden bu yöntem genellikle daha güvenli kabul edilir.

Örneğin, average_score ve rating_count alanının kullanılmasına izin vermemek yerine, istemcilerin yalnızca name, location, city, address, hours ve cuisine alanlarını değiştirmesine izin veren güvenlik kuralları oluşturabilirsiniz.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

Bu, uygulamanızın gelecekteki bir yinelemesinde restoran dokümanlarında bir telephone alanı varsa siz geri dönüp bu alanı güvenlik kurallarınızdaki hasOnly() listesine ekleyene kadar bu alanı düzenleme girişimleri başarısız olur.

Alan türlerini zorunlu kılma

Cloud Firestore öğesinin şemasız olmasının bir diğer etkisi de, belirli alanlarda ne tür verilerin depolanabileceği konusunda veritabanı düzeyinde herhangi bir yaptırım uygulanmamasıdır. Ancak bu, is operatörüyle güvenlik kurallarında zorunlu kılabilirsiniz.

Örneğin, aşağıdaki güvenlik kuralı bir incelemenin score alanının tam sayı, headline, content ve author_name alanlarının dize, review_date alanının ise zaman damgası olması gerektiğini zorunlu kılar.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

is operatörü için geçerli veri türleri bool, bytes, float, int, list, latlng, number, path, map, string ve timestamp'dir. is operatörü constraint, duration, set ve map_diff veri türlerini de destekler. Ancak bunlar güvenlik kuralları dilinin kendisi tarafından oluşturulduğundan ve istemciler tarafından oluşturulmadığından en pratik uygulamalarda bunları nadiren kullanırsınız.

list ve map veri türleri, genel türleri veya tür bağımsız değişkenlerini desteklemez. Diğer bir deyişle, belirli bir alanın liste veya harita içermesini zorunlu kılmak için güvenlik kurallarını kullanabilirsiniz ancak bir alanın tüm tam sayıların veya tüm dizelerin listesini içermesini zorunlu kılamazsınız.

Benzer şekilde, bir liste veya haritadaki belirli girişler için tür değerlerini zorunlu kılmak üzere güvenlik kurallarını kullanabilirsiniz (sırasıyla parantez gösterimi veya anahtar adlarını kullanarak). Ancak bir harita veya listedeki tüm üyelerin veri türlerini aynı anda zorunlu kılmak için kısayol yoktur.

Örneğin, aşağıdaki kurallar bir dokümanda tags alanının bir liste içermesini ve ilk girişin dize olmasını sağlar. Ayrıca product alanının, bir dize olan ürün adını ve tam sayı olan bir miktarı içeren bir harita içermesini sağlar.

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

Alan türleri hem doküman oluştururken hem de güncellerken zorunlu kılınmalıdır. Bu nedenle, güvenlik kurallarınızın hem oluşturma hem de güncelleme bölümlerinde çağırabileceğiniz bir yardımcı işlev oluşturmayı düşünebilirsiniz.

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

İsteğe bağlı alanlar için türleri zorunlu kılma

foo'ın bulunmadığı bir belgede request.resource.data.foo çağrısının hata verdiğini ve bu nedenle bu çağrıyı yapan tüm güvenlik kurallarının isteği reddettiğini unutmayın. Bu durumu request.resource.data'da get yöntemini kullanarak çözebilirsiniz. get yöntemi, haritadan aldığınız alan mevcut değilse bu alan için varsayılan bir bağımsız değişken sağlamanıza olanak tanır.

Örneğin, inceleme dokümanları da doğrulamak istediğiniz isteğe bağlı bir photo_url alanı ve isteğe bağlı bir tags alanı içeriyorsa (sırasıyla dize ve liste) reviewFieldsAreValidTypes işlevini aşağıdaki gibi yeniden yazarak bunu yapabilirsiniz:

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

Bu, tags alanının bulunduğu ancak liste olmayan dokümanları reddeder. tags (veya photo_url) alanı içermeyen dokümanlara ise izin verir.

Kısmi yazmalara hiçbir zaman izin verilmez

Cloud Firestore Security Rules ile ilgili son bir not da, istemcinin bir dokümanda değişiklik yapmasına izin vermeleri veya düzenlemenin tamamını reddetmeleridir. Dokümanınızdaki bazı alanlara yazma işlemlerini kabul ederken aynı işlemde diğerlerini reddeden güvenlik kuralları oluşturamazsınız.