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

تستند هذه الصفحة إلى المفاهيم الواردة في تنظيم قواعد الأمان وكتابة شروط قواعد الأمان لشرح كيفية استخدام Cloud Firestore Security Rules لإنشاء قواعد تسمح للعملاء بإجراء عمليات على بعض الحقول في المستند وليس غيرها.

قد تحتاج أحيانًا إلى التحكّم في التغييرات التي تطرأ على مستند ليس على مستوى المستند، بل على مستوى الحقل.

على سبيل المثال، قد تريد السماح لعميل بإنشاء مستند أو تغييره، ولكن لا تريد السماح له بتعديل حقول معيّنة في ذلك المستند. أو يمكنك فرض أن يحتوي أي مستند ينشئه العميل دائمًا على مجموعة معيّنة من الحقول. يتناول هذا الدليل كيفية تنفيذ بعض هذه المهام باستخدام Cloud Firestore Security Rules.

السماح بالوصول للقراءة فقط في حقول معيّنة

يتم إجراء عمليات القراءة في Cloud Firestore على مستوى المستند. يمكنك إما retrieving the full document، أو retrieving nothing. لا تتوفّر طريقة لاسترداد جزء من المستند. من المستحيل استخدام قواعد الأمان وحدها لمنع المستخدمين من قراءة حقول معيّنة في مستند.

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

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