التحكم في الوصول إلى حقول محددة

تستند هذه الصفحة إلى المفاهيم الواردة في تنظيم قواعد الأمان وكتابة شروط لقواعد الأمان لشرح كيفية استخدام 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 هو نظام غير مرتبط بمخطط، ما يعني أنّه لا توجد قيود على مستوى قاعدة البيانات بشأن الحقول التي يتضمّنها المستند. على الرغم من أنّ هذه المرونة يمكن أن تسهّل عملية التطوير، ستكون هناك أوقات تريد فيها التأكّد من أنّ العملاء يمكنهم إنشاء مستندات تحتوي على حقول معيّنة فقط، أو لا تحتوي على حقول أخرى.

يمكنك إنشاء هذه القواعد من خلال فحص طريقة keys للكائن request.resource.data. هذه قائمة بجميع الحقول التي يحاول العميل كتابتها في هذا المستند الجديد. من خلال الجمع بين مجموعة الحقول هذه مع دوال مثل 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() مع قائمة بالحقول المحظورة. تعرض هذه الطريقة القيمة &quot;صحيح&quot; إذا كان المستند يتضمّن أيًا من هذه الحقول، لذا من المحتمل أنّك تريد عكس النتيجة من أجل حظر حقول معيّنة.

على سبيل المثال، في المثال التالي، لا يُسمح للعملاء بإنشاء مستند يحتوي على الحقل 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، وهو كائن يحتوي على جميع التغييرات بين خريطتَين مختلفتَين.

من خلال استدعاء طريقة affectedKeys() على mapDiff هذا، يمكنك التوصّل إلى مجموعة من الحقول التي تم تغييرها في عملية تعديل. بعد ذلك، يمكنك استخدام دوال مثل 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.foo سترفض الطلب. يمكنك التعامل مع هذه الحالة باستخدام طريقة get في request.resource.data. تتيح لك الطريقة 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 إما أن تسمح للعميل بإجراء تغيير على مستند، أو ترفض التعديل بأكمله. لا يمكنك إنشاء قواعد أمان تقبل عمليات الكتابة في بعض الحقول في المستند وترفضها في حقول أخرى ضمن العملية نفسها.