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

इस पेज पर, सुरक्षा नियमों को व्यवस्थित करना और सुरक्षा नियमों के लिए शर्तें लिखना में बताए गए कॉन्सेप्ट के आधार पर, यह बताया गया है कि 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

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

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() का इस्तेमाल करें. अगर किसी दस्तावेज़ में इनमें से कोई भी फ़ील्ड मौजूद है, तो यह तरीका 'सही' के तौर पर काम करता है. इसलिए, कुछ फ़ील्ड को इस्तेमाल करने से रोकने के लिए, आपको नतीजे को 'गलत' के तौर पर दिखाना होगा.

उदाहरण के लिए, नीचे दिए गए उदाहरण में, क्लाइंट को ऐसा दस्तावेज़ बनाने की अनुमति नहीं है जिसमें 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
      }
    }
  }
}

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

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 के बारे में एक और बात यह है कि वे क्लाइंट को दस्तावेज़ में बदलाव करने की अनुमति देते हैं या पूरे बदलाव को अस्वीकार कर देते हैं. ऐसे सुरक्षा नियम नहीं बनाए जा सकते जो आपके दस्तावेज़ के कुछ फ़ील्ड में डेटा लिखने की अनुमति देते हों, जबकि उसी ऑपरेशन में अन्य फ़ील्ड में डेटा लिखने की अनुमति न देते हों.