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