Belirli alanlara erişimi kontrol etme

Bu sayfada, Güvenlik Kurallarını Yapılandırma ve Güvenlik Kuralları İçin Koşullar Yazma sayfalarındaki kavramlardan yararlanarak Cloud Firestore Security Rules ile istemcilerin bir dokümandaki bazı alanlarda işlem yapmasına izin veren ancak diğerlerinde izin vermeyen kuralları nasıl oluşturabileceğiniz açıklanmaktadı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 belge oluşturmasına veya değiştirmesine izin verebilir ancak bu belgedeki belirli alanları düzenlemesine izin vermeyebilirsiniz. Alternatif olarak, bir müşterinin her zaman oluşturduğu belgelerin belirli bir alan grubunu içermesini zorunlu kılabilirsiniz. Bu kılavuzda, Cloud Firestore Security Rules kullanarak bu görevlerden bazılarını nasıl tamamlayabileceğiniz açıklanmaktadır.

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

Cloud Firestore okuma işlemleri belge düzeyinde yapılır. Ya belgenin tamamını alırsınız ya da hiçbir şey almazsınız. Kısmi dokümanları geri almanın bir yolu yoktur. Kullanıcıların bir dokümandaki 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 gizli tutmak istediğiniz belirli alanlar varsa bunları ayrı bir dokümana yerleştirmeniz en iyi yöntemdir. Örneğin, privatealt koleksiyonda şu şekilde bir doküman oluşturabilirsiniz:

/employees/{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 role özel kimlik doğrulama talebi Finance değerine eşit olan kullanıcıların bir çalışanın finansal bilgilerini görüntüleyebileceğini belirtmek için özel kimlik doğrulama talepleri 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'
      }
    }
  }
}

Belge oluşturma sırasında alanları kısıtlama

Cloud Firestore şemasızdır. Bu nedenle, bir belgenin hangi alanları içereceği konusunda veritabanı düzeyinde kısıtlama yoktur. Bu esneklik geliştirme sürecini kolaylaştırsa da istemcilerin yalnızca belirli alanları içeren veya başka alanları içermeyen dokümanlar oluşturabilmesini sağlamak 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'ye ekleyebileceği belge türlerini kısıtlayan bir mantık ekleyebilirsiniz.

Yeni dokümanlarda belirli alanların zorunlu olması

restaurant koleksiyonunda oluşturulan tüm dokümanların en az bir name, location ve city alanı içerdiğinden emin olmak istediğinizi varsayalım. Bunu, yeni dokümandaki anahtar listesinde hasAll() işlevini ç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 başka alanlarla da oluşturulmasına olanak tanır ancak bir istemci tarafından oluşturulan tüm belgelerin en az bu üç alanı içermesini sağlar.

Yeni dokümanlarda belirli alanların yasaklanması

Benzer şekilde, yasaklanmış alanların listesine karşı hasAny() kullanarak istemcilerin belirli alanları içeren dokümanlar oluşturmasını engelleyebilirsiniz. Bu yöntem, bir dokümanda bu alanlardan herhangi biri varsa doğru olarak değerlendirilir. Bu nedenle, belirli alanları yasaklamak için sonucu muhtemelen olumsuzlamak istersiniz.

Örneğin, aşağıdaki örnekte, bu alanlar daha sonra bir sunucu çağrısıyla 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 dokümanlar için izin verilen alanlar listesi oluşturma

Yeni belgelerde belirli alanları yasaklamak yerine, yeni belgelerde açıkça izin verilen alanların bir listesini oluşturmak isteyebilirsiniz. Ardından, oluşturulan yeni dokümanların yalnızca bu alanları (veya bu alanların bir alt kümesini) içerdiğinden ve başka alan içermediğ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ın zorunlu olmasını ve diğer alanlara izin verilmesini sağlamak 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 gerekirken address, hours ve cuisine alanlarına isteğe bağlı olarak 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 hayattaki bir senaryoda, kodunuzu kopyalamamak ve isteğe bağlı ile zorunlu alanları tek bir listede daha kolay birleştirmek için bu mantığı yardımcı bir 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üncellemede alanları kısıtlama

Yaygın bir güvenlik uygulaması, istemcilerin yalnızca bazı alanları düzenlemesine izin vermek ve diğerlerini düzenlemesine izin vermemektir. Bu işlemi yalnızca önceki bölümde açıklanan request.resource.data.keys() listesine bakarak yapamazsınız. Çünkü bu liste, dokümanın güncellemeden sonraki görünümünü temsil eder ve bu nedenle istemcinin değiştirmediği alanları da içerir.

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

Bu mapDiff üzerinde affectedKeys() yöntemini çağırarak bir düzenlemede değiştirilen alanlar kümesi oluşturabilirsiniz. Ardından, bu kümenin belirli öğeler içerdiğinden (veya 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 kullanarak ve 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 verebilir ancak ortalama puanını veya yorum sayısını değiştirmesine izin vermeyebilirsiniz.

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, değiştirilmesini istediğiniz alanların listesini belirtmek için hasOnly() işlevini de kullanabilirsiniz. Bu yöntem genellikle daha güvenli kabul edilir. Çünkü güvenlik kurallarınızda açıkça izin verene kadar yeni doküman alanlarına yazma işlemi varsayılan olarak engellenir.

Örneğin, average_score ve rating_count alanı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 sürümünde restoran belgeleri telephone alanını içeriyorsa güvenlik kurallarınızda bu alanı hasOnly() listesine geri dönüp ekleyene kadar bu alanı düzenleme girişimlerinin başarısız olacağı anlamına gelir.

Alan türlerini zorunlu kılma

Cloud Firestore'nın şemasız olmasının bir diğer etkisi de belirli alanlarda hangi tür verilerin depolanabileceği konusunda veritabanı düzeyinde zorlama olmamasıdır. Bunu güvenlik kurallarında is operatörüyle zorunlu kılabilirsiniz.

Örneğin, aşağıdaki güvenlik kuralı, bir yorumun score alanının tam sayı, headline, content ve author_name alanlarının dize, review_date alanının ise zaman damgası olmasını 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ğu ve istemciler tarafından oluşturulmadığı için çoğu pratik uygulamada nadiren kullanılır.

list ve map veri türleri, genel türleri veya tür bağımsız değişkenlerini desteklemez. Başka 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 listedeki veya haritadaki belirli girişler için tür değerlerini zorunlu kılmak üzere güvenlik kurallarını kullanabilirsiniz (sırasıyla köşeli parantez gösterimini veya anahtar adlarını kullanarak). Ancak bir haritadaki veya listedeki tüm üyelerin veri türlerini aynı anda zorunlu kılmak için bir kısayol yoktur.

Örneğin, aşağıdaki kurallar bir dokümandaki tags alanının liste içerdiğinden ve ilk girişin dize olduğundan emin olur. Ayrıca, product alanının, dize olan bir ürün adı ve tam sayı olan bir miktar içeren bir harita içerdiğinden de emin olunur.

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 belge oluşturulurken hem de güncellenirken zorunlu olmalı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 öğesinin bulunmadığı bir dokümanda request.resource.data.foo öğesinin çağrılmasının hataya neden olduğunu ve bu nedenle bu çağrıyı yapan tüm güvenlik kurallarının isteği reddedeceğini unutmayın. Bu durumu request.resource.data adresinde get yöntemini kullanarak çözebilirsiniz. get yöntemi, bir 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 belgelerinde isteğe bağlı bir photo_url alanı ve sırasıyla dize ve liste olduklarını doğrulamak istediğiniz isteğe bağlı bir tags alanı da varsa bunu reviewFieldsAreValidTypes işlevini aşağıdakine benzer şekilde yeniden yazarak gerçekleştirebilirsiniz:

  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ı reddederken tags (veya photo_url) alanı içermeyen dokümanlara izin vermeye devam eder.

Kısmi yazma işlemlerine hiçbir zaman izin verilmez

Cloud Firestore Security Rules ile ilgili son bir not: Bu kullanıcılar, istemcinin bir dokümanda değişiklik yapmasına izin verir veya tüm düzenlemeyi reddeder. Aynı işlemde bazı alanlara yazma işlemlerini kabul ederken diğerlerini reddeden güvenlik kuralları oluşturamazsınız.