खास फ़ील्ड का ऐक्सेस कंट्रोल करना

इस पेज पर, सुरक्षा नियमों को स्ट्रक्चर करना और सुरक्षा नियमों के लिए शर्तें लिखना लेखों में दिए गए कॉन्सेप्ट के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि Cloud Firestore Security Rules का इस्तेमाल करके ऐसे नियम कैसे बनाए जा सकते हैं जिनकी मदद से क्लाइंट, किसी दस्तावेज़ के कुछ फ़ील्ड पर कार्रवाइयां कर सकें, लेकिन अन्य फ़ील्ड पर नहीं.

ऐसा हो सकता है कि आपको किसी दस्तावेज़ में बदलावों को कंट्रोल करना हो. हालांकि, आपको ऐसा दस्तावेज़ लेवल पर नहीं, बल्कि फ़ील्ड लेवल पर करना हो.

उदाहरण के लिए, हो सकता है कि आपको किसी क्लाइंट को कोई दस्तावेज़ बनाने या उसमें बदलाव करने की अनुमति देनी हो. हालांकि, आपको उसे उस दस्तावेज़ के कुछ फ़ील्ड में बदलाव करने की अनुमति नहीं देनी हो. इसके अलावा, ऐसा भी हो सकता है कि आपको यह ज़रूरी करना हो कि क्लाइंट हमेशा जो भी दस्तावेज़ बनाए उसमें फ़ील्ड का एक तय सेट शामिल हो. इस गाइड में, Cloud Firestore Security Rules का इस्तेमाल करके इनमें से कुछ टास्क पूरे करने का तरीका बताया गया है.

सिर्फ़ कुछ फ़ील्ड के लिए पढ़ने का ऐक्सेस देना

Cloud Firestore में पढ़ने की कार्रवाइयां, दस्तावेज़ के लेवल पर की जाती हैं. आपको पूरा दस्तावेज़ मिलेगा या कुछ भी नहीं मिलेगा. दस्तावेज़ के कुछ हिस्से को वापस नहीं लाया जा सकता. सुरक्षा के नियमों का इस्तेमाल करके, उपयोगकर्ताओं को किसी दस्तावेज़ में मौजूद कुछ फ़ील्ड पढ़ने से नहीं रोका जा सकता.

अगर आपको किसी दस्तावेज़ के कुछ फ़ील्ड को कुछ लोगों से छिपाना है, तो उन्हें किसी दूसरे दस्तावेज़ में डालना सबसे अच्छा तरीका होगा. उदाहरण के लिए, private सब-कलेक्शन में इस तरह से कोई दस्तावेज़ बनाया जा सकता है:

/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

इसके बाद, सुरक्षा से जुड़े ऐसे नियम जोड़े जा सकते हैं जिनमें दोनों कलेक्शन के लिए ऐक्सेस के अलग-अलग लेवल हों. इस उदाहरण में, हम कस्टम ऑथ क्लेम का इस्तेमाल कर रहे हैं. इससे यह तय किया जा सकता है कि Finance के बराबर role वाले कस्टम ऑथ क्लेम वाले उपयोगकर्ता ही किसी कर्मचारी की वित्तीय जानकारी देख सकते हैं.

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'
      }
    }
  }
}

दस्तावेज़ बनाने के लिए फ़ील्ड पर पाबंदी लगाना

Cloud Firestore में कोई स्कीमा नहीं होती. इसका मतलब है कि डेटाबेस लेवल पर, किसी दस्तावेज़ में मौजूद फ़ील्ड के लिए कोई पाबंदी नहीं होती. इस सुविधा से डेवलपमेंट को आसान बनाया जा सकता है. हालांकि, कुछ मामलों में आपको यह पक्का करना होगा कि क्लाइंट सिर्फ़ ऐसे दस्तावेज़ बना सकें जिनमें कुछ खास फ़ील्ड शामिल हों या जिनमें अन्य फ़ील्ड शामिल न हों.

request.resource.data ऑब्जेक्ट के keys तरीके की जांच करके, ये नियम बनाए जा सकते हैं. यह उन सभी फ़ील्ड की सूची है जिनमें क्लाइंट इस नए दस्तावेज़ में डेटा लिखने की कोशिश कर रहा है. इन फ़ील्ड के सेट को hasOnly() या hasAny() जैसे फ़ंक्शन के साथ मिलाकर, ऐसा लॉजिक जोड़ा जा सकता है जो यह तय करता है कि कोई उपयोगकर्ता Cloud Firestore में किस तरह के दस्तावेज़ जोड़ सकता है.

नए दस्तावेज़ों में कुछ फ़ील्ड ज़रूरी होना

मान लें कि आपको यह पक्का करना है कि restaurant कलेक्शन में बनाए गए सभी दस्तावेज़ों में कम से कम name, location, और city फ़ील्ड मौजूद हों. इसके लिए, नए दस्तावेज़ में मौजूद कुंजियों की सूची में hasAll() को कॉल करें.

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']);
    }
  }
}

इससे रेस्टोरेंट को अन्य फ़ील्ड के साथ भी बनाया जा सकता है. हालांकि, इससे यह पक्का होता है कि क्लाइंट की ओर से बनाए गए सभी दस्तावेज़ों में कम से कम ये तीन फ़ील्ड मौजूद हों.

नए दस्तावेज़ों में कुछ फ़ील्ड इस्तेमाल करने की अनुमति न देना

इसी तरह, hasAny() का इस्तेमाल करके, क्लाइंट को ऐसे दस्तावेज़ बनाने से रोका जा सकता है जिनमें कुछ फ़ील्ड शामिल हों. इसके लिए, आपको उन फ़ील्ड की सूची बनानी होगी जिन्हें शामिल करने की अनुमति नहीं है. अगर किसी दस्तावेज़ में इनमें से कोई भी फ़ील्ड मौजूद होता है, तो यह तरीका true वैल्यू देता है. इसलिए, आपको शायद कुछ फ़ील्ड को अनुमति न देने के लिए, नतीजे को उलटना पड़े.

उदाहरण के लिए, यहां दिए गए उदाहरण में क्लाइंट को ऐसा दस्तावेज़ बनाने की अनुमति नहीं है जिसमें average_score या rating_count फ़ील्ड शामिल हो. ऐसा इसलिए, क्योंकि इन फ़ील्ड को बाद में सर्वर कॉल के ज़रिए जोड़ा जाएगा.

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']));
    }
  }
}

नए दस्तावेज़ों के लिए, फ़ील्ड की अनुमति वाली सूची बनाना

नए दस्तावेज़ों में कुछ फ़ील्ड इस्तेमाल करने की अनुमति न देने के बजाय, आपको सिर्फ़ उन फ़ील्ड की सूची बनानी चाहिए जिन्हें नए दस्तावेज़ों में इस्तेमाल करने की अनुमति है. इसके बाद, hasOnly() फ़ंक्शन का इस्तेमाल करके यह पक्का किया जा सकता है कि बनाए गए नए दस्तावेज़ों में सिर्फ़ ये फ़ील्ड (या इन फ़ील्ड का सबसेट) शामिल हों और कोई दूसरा फ़ील्ड न हो.

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']));
    }
  }
}

ज़रूरी और ज़रूरी नहीं फ़ील्ड को एक साथ इस्तेमाल करना

सुरक्षा नियमों में, hasAll और hasOnly ऑपरेशनों को एक साथ इस्तेमाल किया जा सकता है. इससे कुछ फ़ील्ड के लिए ज़रूरी शर्तें तय की जा सकती हैं और कुछ फ़ील्ड के लिए अनुमति दी जा सकती है. उदाहरण के लिए, इस उदाहरण में यह ज़रूरी है कि सभी नए दस्तावेज़ों में name, location, और city फ़ील्ड शामिल हों. साथ ही, इसमें address, hours, और cuisine फ़ील्ड शामिल करने का विकल्प भी दिया गया है.

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']));
    }
  }
}

असल दुनिया में, आपको इस लॉजिक को हेल्पर फ़ंक्शन में ले जाना पड़ सकता है, ताकि आपके कोड को डुप्लीकेट होने से बचाया जा सके. साथ ही, ज़रूरी और वैकल्पिक फ़ील्ड को एक ही सूची में आसानी से जोड़ा जा सके. जैसे:

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']);
    }
  }
}

अपडेट करने पर फ़ील्ड पर पाबंदी लगाना

सुरक्षा के लिए, क्लाइंट को सिर्फ़ कुछ फ़ील्ड में बदलाव करने की अनुमति दी जाती है. सिर्फ़ पिछले सेक्शन में बताई गई request.resource.data.keys() सूची को देखकर, यह काम नहीं किया जा सकता. इसकी वजह यह है कि यह सूची, अपडेट के बाद पूरे दस्तावेज़ को दिखाती है. इसलिए, इसमें वे फ़ील्ड भी शामिल होंगे जिनमें क्लाइंट ने बदलाव नहीं किया है.

हालांकि, diff() फ़ंक्शन का इस्तेमाल करके, request.resource.data की तुलना resource.data ऑब्जेक्ट से की जा सकती है. यह ऑब्जेक्ट, अपडेट से पहले डेटाबेस में मौजूद दस्तावेज़ को दिखाता है. इससे एक mapDiff ऑब्जेक्ट बनता है. यह एक ऐसा ऑब्जेक्ट होता है जिसमें दो अलग-अलग मैप के बीच हुए सभी बदलाव शामिल होते हैं.

इस mapDiff पर affectedKeys() मेथड को कॉल करके, उन फ़ील्ड का सेट बनाया जा सकता है जिनमें बदलाव किया गया था. इसके बाद, hasOnly() या hasAny() जैसे फ़ंक्शन का इस्तेमाल करके, यह पक्का किया जा सकता है कि इस सेट में कुछ आइटम शामिल हैं या नहीं.

कुछ फ़ील्ड में बदलाव होने से रोकना

affectedKeys() से जनरेट किए गए सेट पर hasAny() तरीके का इस्तेमाल करके और फिर नतीजे को नकारकर, किसी भी ऐसे क्लाइंट अनुरोध को अस्वीकार किया जा सकता है जो उन फ़ील्ड को बदलने की कोशिश करता है जिन्हें आपको नहीं बदलना है.

उदाहरण के लिए, हो सकता है कि आपको क्लाइंट को किसी रेस्टोरेंट के बारे में जानकारी अपडेट करने की अनुमति देनी हो, लेकिन उसके औसत स्कोर या समीक्षाओं की संख्या में बदलाव करने की अनुमति न देनी हो.

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']));
    }
  }
}

सिर्फ़ कुछ फ़ील्ड में बदलाव करने की अनुमति देना

जिन फ़ील्ड में बदलाव नहीं करना है उन्हें तय करने के बजाय, hasOnly() फ़ंक्शन का इस्तेमाल करके, उन फ़ील्ड की सूची तय की जा सकती है जिनमें बदलाव करना है. इसे आम तौर पर ज़्यादा सुरक्षित माना जाता है, क्योंकि किसी भी नए दस्तावेज़ फ़ील्ड में लिखने की अनुमति डिफ़ॉल्ट रूप से तब तक नहीं दी जाती, जब तक कि आप सुरक्षा से जुड़े नियमों में साफ़ तौर पर इसकी अनुमति न दें.

उदाहरण के लिए, average_score और rating_count फ़ील्ड को अनुमति न देने के बजाय, सुरक्षा से जुड़े ऐसे नियम बनाए जा सकते हैं जिनसे क्लाइंट सिर्फ़ name, location, city, address, hours, और cuisine फ़ील्ड में बदलाव कर सकें.

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']));
    }
  }
}

इसका मतलब है कि अगर आपके ऐप्लिकेशन के किसी आने वाले वर्शन में, रेस्टोरेंट के दस्तावेज़ों में telephone फ़ील्ड शामिल है, तो उस फ़ील्ड में बदलाव करने की कोशिश करने पर, बदलाव नहीं हो पाएगा. ऐसा तब तक होगा, जब तक आप वापस जाकर उस फ़ील्ड को सुरक्षा नियमों में मौजूद hasOnly() सूची में नहीं जोड़ देते.

फ़ील्ड टाइप लागू करना

Cloud Firestore में स्कीमा नहीं होता है. इसका एक और असर यह होता है कि डेटाबेस लेवल पर यह लागू नहीं किया जाता कि किसी फ़ील्ड में किस तरह का डेटा सेव किया जा सकता है. सुरक्षा के नियमों में इसे लागू किया जा सकता है. हालांकि, ऐसा is ऑपरेटर की मदद से किया जा सकता है.

उदाहरण के लिए, यहां दिए गए सुरक्षा नियम से यह पता चलता है कि समीक्षा के score फ़ील्ड में पूर्णांक होना चाहिए. साथ ही, headline, content, और author_name फ़ील्ड में स्ट्रिंग होनी चाहिए और review_date फ़ील्ड में टाइमस्टैंप होना चाहिए.

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 ऑपरेटर के लिए मान्य डेटा टाइप ये हैं: bool, bytes, float, int, list, latlng, number, path, map, string, और timestamp. is ऑपरेटर, constraint, duration, set, और map_diff डेटा टाइप के साथ भी काम करता है. हालांकि, ये सुरक्षा नियमों की भाषा से जनरेट होते हैं, न कि क्लाइंट से. इसलिए, इनका इस्तेमाल बहुत कम किया जाता है.

list और map डेटा टाइप, सामान्य या टाइप आर्ग्युमेंट के साथ काम नहीं करते. दूसरे शब्दों में कहें, तो सुरक्षा नियमों का इस्तेमाल करके यह लागू किया जा सकता है कि किसी फ़ील्ड में सूची या मैप शामिल हो. हालांकि, यह लागू नहीं किया जा सकता कि किसी फ़ील्ड में सभी पूर्णांक या सभी स्ट्रिंग की सूची शामिल हो.

इसी तरह, सुरक्षा नियमों का इस्तेमाल करके, किसी सूची या मैप में मौजूद कुछ खास एंट्री के लिए टाइप वैल्यू लागू की जा सकती हैं. इसके लिए, ब्रैकेट नोटेशन या कुंजी के नामों का इस्तेमाल किया जाता है. हालांकि, किसी मैप या सूची में मौजूद सभी सदस्यों के डेटा टाइप को एक साथ लागू करने का कोई शॉर्टकट नहीं है.

उदाहरण के लिए, यहां दिए गए नियम यह पक्का करते हैं कि किसी दस्तावेज़ में मौजूद tags फ़ील्ड में एक सूची शामिल हो और पहली एंट्री एक स्ट्रिंग हो. इससे यह भी पक्का होता है कि product फ़ील्ड में एक मैप शामिल हो. इस मैप में एक प्रॉडक्ट का नाम शामिल हो. यह नाम एक स्ट्रिंग हो और इसकी मात्रा एक पूर्णांक हो.

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
      }
    }
  }
}

दस्तावेज़ बनाते और अपडेट करते समय, फ़ील्ड टाइप लागू होने चाहिए. इसलिए, आपको एक हेल्पर फ़ंक्शन बनाना चाहिए, जिसे सुरक्षा के नियमों के create और update, दोनों सेक्शन में कॉल किया जा सके.

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
      }
    }
  }
}

ज़रूरी नहीं वाले फ़ील्ड के लिए टाइप लागू करना

यह याद रखना ज़रूरी है कि जिस दस्तावेज़ में foo मौजूद नहीं है उस पर request.resource.data.foo को कॉल करने से गड़बड़ी होती है. इसलिए, उस कॉल को करने वाला कोई भी सुरक्षा नियम, अनुरोध को अस्वीकार कर देगा. request.resource.data पर get तरीके का इस्तेमाल करके, इस समस्या को ठीक किया जा सकता है. get तरीके की मदद से, मैप से वापस लाए जा रहे फ़ील्ड के लिए डिफ़ॉल्ट आर्ग्युमेंट दिया जा सकता है. ऐसा तब किया जा सकता है, जब वह फ़ील्ड मौजूद न हो.

उदाहरण के लिए, अगर समीक्षा वाले दस्तावेज़ों में एक वैकल्पिक photo_url फ़ील्ड और एक वैकल्पिक tags फ़ील्ड भी शामिल है. आपको यह पुष्टि करनी है कि ये फ़ील्ड, स्ट्रिंग और सूचियां हैं, तो reviewFieldsAreValidTypes फ़ंक्शन को इस तरह से फिर से लिखा जा सकता है:

  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;
  }

इससे उन दस्तावेज़ों को अस्वीकार कर दिया जाता है जिनमें tags मौजूद है, लेकिन वह सूची नहीं है. हालांकि, उन दस्तावेज़ों को अनुमति दी जाती है जिनमें tags (या photo_url) फ़ील्ड मौजूद नहीं है.

आंशिक तौर पर लिखने की अनुमति कभी नहीं दी जाती

Cloud Firestore Security Rules के बारे में एक आखिरी बात यह है कि वे क्लाइंट को किसी दस्तावेज़ में बदलाव करने की अनुमति देते हैं या वे पूरे बदलाव को अस्वीकार कर देते हैं. ऐसी सुरक्षा से जुड़े नियम नहीं बनाए जा सकते जो आपके दस्तावेज़ के कुछ फ़ील्ड में लिखने की अनुमति देते हों, लेकिन उसी ऑपरेशन में अन्य फ़ील्ड में लिखने की अनुमति न देते हों.