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