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

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

`object` के 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() जैसे फ़ंक्शन का इस्तेमाल करके, यह पक्का किया जा सकता है कि इस सेट में कुछ आइटम शामिल हों या न हों.

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

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

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

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

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

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