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