يستند هذا الدليل إلى دليل التعرّف على اللغة الأساسية لقواعد الأمان في Firebase لتوضيح كيفية إضافة شروط إلى قواعد الأمان في "قاعدة بيانات Firebase الآنية الاستجابة".
إنّ الوحدة الأساسية لقواعد الأمان في Realtime Database هي الشرط. الشرط هو تعبير منطقي يحدّد ما إذا كان يجب السماح بعملية معيّنة أو رفضها. بالنسبة إلى القواعد الأساسية، يمكن استخدام القيم الحرفية true
وfalse
كشروط بشكل مثالي. ومع ذلك، تتيح لك لغة "قواعد الأمان" في Realtime Database كتابة شروط أكثر تعقيدًا يمكنها تنفيذ ما يلي:
- التحقّق من مصادقة المستخدم
- تقييم البيانات الحالية مقارنةً بالبيانات المُرسَلة حديثًا
- الوصول إلى أجزاء مختلفة من قاعدة البيانات ومقارنتها
- التحقّق من صحة البيانات الواردة
- استخدام بنية طلبات البحث الواردة لمنطق الأمان
استخدام متغيّرات $ لتسجيل أجزاء المسار
يمكنك التقاط أجزاء من المسار للقراءة أو الكتابة من خلال تعريف
متغيرات الالتقاط باستخدام البادئة $
.
يعمل هذا الحرف كحرف بدل، ويخزّن قيمة هذا المفتاح لاستخدامه داخل شروط القواعد:
{ "rules": { "rooms": { // this rule applies to any child of /rooms/, the key for each room id // is stored inside $room_id variable for reference "$room_id": { "topic": { // the room's topic can be changed if the room id has "public" in it ".write": "$room_id.contains('public')" } } } } }
يمكن أيضًا استخدام متغيّرات $
الديناميكية بالتوازي مع أسماء المسارات الثابتة. في هذا المثال، نستخدم المتغيّر $other
لتعريف قاعدة .validate
تضمن ألا يتضمّن widget
أي عناصر فرعية أخرى غير title
وcolor
.
سيتعذّر تنفيذ أي عملية كتابة تؤدي إلى إنشاء عناصر فرعية إضافية.
{ "rules": { "widget": { // a widget can have a title or color attribute "title": { ".validate": true }, "color": { ".validate": true }, // but no other child paths are allowed // in this case, $other means any key excluding "title" and "color" "$other": { ".validate": false } } } }
المصادقة
أحد أنماط قواعد الأمان الأكثر شيوعًا هو التحكّم في إذن الوصول استنادًا إلى حالة مصادقة المستخدم. على سبيل المثال، قد يريد تطبيقك السماح للمستخدمين الذين سجّلوا الدخول فقط بكتابة البيانات.
إذا كان تطبيقك يستخدم خدمة Firebase Authentication، يحتوي المتغيّر request.auth
على معلومات المصادقة الخاصة بالعميل الذي يطلب البيانات.
لمزيد من المعلومات حول request.auth
، يُرجى الاطّلاع على المستندات المرجعية.
تتكامل Firebase Authentication مع Firebase Realtime Database للسماح لك بالتحكّم في إمكانية الوصول إلى البيانات لكل مستخدم على حدة باستخدام شروط. بعد مصادقة المستخدم، سيتم ملء المتغيّر auth
في قواعد أمان Realtime Database بمعلومات المستخدم. وتشمل هذه المعلومات المعرّف الفريد (uid
) بالإضافة إلى بيانات الحساب المرتبط، مثل معرّف Facebook أو عنوان البريد الإلكتروني، ومعلومات أخرى. وفي حال استخدام موفّر مصادقة مخصّص، يمكنك إضافة حقولك الخاصة إلى حمولة المصادقة الخاصة بالمستخدم.
يوضّح هذا القسم كيفية دمج لغة "قواعد الأمان" في Firebase Realtime Database مع معلومات المصادقة الخاصة بالمستخدمين. من خلال الجمع بين هذين المفهومين، يمكنك التحكّم في الوصول إلى البيانات استنادًا إلى هوية المستخدم.
المتغيّر auth
يكون المتغيّر auth
المحدّد مسبقًا في القواعد فارغًا قبل إجراء المصادقة.
بعد مصادقة المستخدم باستخدام Firebase Authentication، سيحتوي على السمات التالية:
مزوِّد الخدمة | طريقة المصادقة المستخدَمة ("password" أو "anonymous" أو "facebook" أو "github" أو "google" أو "twitter"). |
uid | معرّف مستخدم فريد، ويُضمن أن يكون فريدًا لدى جميع مقدّمي الخدمة. |
الرمز المميز |
محتوى رمز التعريف المميز في Firebase Auth لمزيد من التفاصيل، يُرجى الاطّلاع على مستندات المرجع الخاصة بـ
auth.token .
|
في ما يلي مثال على قاعدة تستخدم المتغيّر auth
للتأكّد من أنّ كل مستخدم يمكنه الكتابة إلى مسار خاص به فقط:
{ "rules": { "users": { "$user_id": { // grants write access to the owner of this user account // whose uid must exactly match the key ($user_id) ".write": "$user_id === auth.uid" } } } }
تنظيم قاعدة البيانات لتوفير شروط المصادقة
من المفيد عادةً تنظيم قاعدة البيانات بطريقة تسهّل كتابة Rules. أحد الأنماط الشائعة لتخزين بيانات المستخدمين في Realtime Database هو تخزين جميع المستخدمين في عقدة users
واحدة تكون العناصر التابعة لها هي قيم uid
لكل مستخدم. إذا أردت حصر إمكانية الوصول إلى هذه البيانات بحيث لا يمكن إلا للمستخدم الذي سجّل الدخول الاطّلاع على بياناته، ستكون القواعد على النحو التالي.
{ "rules": { "users": { "$uid": { ".read": "auth !== null && auth.uid === $uid" } } } }
التعامل مع المطالبات المخصّصة للمصادقة
بالنسبة إلى التطبيقات التي تتطلّب عناصر تحكّم مخصّصة في الوصول للمستخدمين المختلفين، تتيح Firebase Authentication للمطوّرين ضبط المطالبات على مستخدم Firebase.
يمكن الوصول إلى هذه المطالبات في المتغيّرauth.token
في قواعدك.
في ما يلي مثال على قواعد تستخدم المطالبة المخصّصة hasEmergencyTowel
:
{ "rules": { "frood": { // A towel is about the most massively useful thing an interstellar // hitchhiker can have ".read": "auth.token.hasEmergencyTowel === true" } } }
يمكن للمطوّرين الذين ينشئون رموزًا مميّزة مخصّصة للمصادقة إضافة مطالبات إلى هذه الرموز بشكل اختياري. تتوفّر هذه المطالبات في المتغيّر auth.token
في قواعدك.
البيانات الحالية مقارنةً بالبيانات الجديدة
يُستخدَم المتغيّر data
المعرَّف مسبقًا للإشارة إلى البيانات قبل إجراء عملية كتابة. في المقابل، يحتوي المتغيّر newData
على البيانات الجديدة التي ستكون متاحة إذا تمت عملية الكتابة بنجاح.
يمثّل newData
النتيجة المدمجة للبيانات الجديدة التي تتم كتابتها والبيانات الحالية.
لتوضيح ذلك، ستسمح لنا هذه القاعدة بإنشاء سجلات جديدة أو حذف السجلات الحالية، ولكن ليس بإجراء تغييرات على البيانات الحالية غير الفارغة:
// we can write as long as old data or new data does not exist // in other words, if this is a delete or a create, but not an update ".write": "!data.exists() || !newData.exists()"
الإشارة إلى البيانات في مسارات أخرى
يمكن استخدام أي بيانات كمعيار للقواعد. باستخدام المتغيّرات المحدّدة مسبقًا root
وdata
وnewData
، يمكننا الوصول إلى أي مسار كما سيكون قبل حدث الكتابة أو بعده.
لنأخذ المثال التالي الذي يسمح بعمليات الكتابة ما دامت قيمة العقدة /allow_writes/
هي true
، ولم يتم ضبط العلامة readOnly
في العقدة الرئيسية، وكان هناك عنصر فرعي باسم foo
في البيانات المكتوبة حديثًا:
".write": "root.child('allow_writes').val() === true && !data.parent().child('readOnly').exists() && newData.child('foo').exists()"
التحقّق من صحة البيانات
يجب فرض بنى البيانات والتحقّق من صحة تنسيق البيانات ومحتواها باستخدام قواعد .validate
، والتي لا يتم تنفيذها إلا بعد نجاح قاعدة .write
لمنح إذن الوصول. في ما يلي نموذج لتعريف قاعدة .validate
لا تسمح إلا بالتواريخ بالتنسيق YYYY-MM-DD بين السنوات 1900 و2099، ويتم التحقّق من ذلك باستخدام تعبير عادي.
".validate": "newData.isString() && newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"
.validate
هي النوع الوحيد من قواعد الأمان التي لا تتتالى. إذا لم تستوفِ أي قاعدة من قواعد التحقّق من الصحة أي سجلّ فرعي، سيتم رفض عملية الكتابة بأكملها.
بالإضافة إلى ذلك، يتم تجاهل عمليات التحقّق من صحة التعريفات عند حذف البيانات (أي عندما تكون القيمة الجديدة التي تتم كتابتها هي null
).
قد تبدو هذه النقاط بسيطة، ولكنّها في الواقع ميزات مهمة لكتابة قواعد أمان فعّالة في "قاعدة بيانات Firebase الآنية الاستجابة". يجب مراعاة القواعد التالية:
{ "rules": { // write is allowed for all paths ".write": true, "widget": { // a valid widget must have attributes "color" and "size" // allows deleting widgets (since .validate is not applied to delete rules) ".validate": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99 ".validate": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical // /valid_colors/ index ".validate": "root.child('valid_colors/' + newData.val()).exists()" } } } }
مع وضع هذا النوع في الاعتبار، اطّلِع على نتائج عمليات الكتابة التالية:
JavaScript
var ref = db.ref("/widget"); // PERMISSION_DENIED: does not have children color and size ref.set('foo'); // PERMISSION DENIED: does not have child color ref.set({size: 22}); // PERMISSION_DENIED: size is not a number ref.set({ size: 'foo', color: 'red' }); // SUCCESS (assuming 'blue' appears in our colors list) ref.set({ size: 21, color: 'blue'}); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child('size').set(99);
Objective-C
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"]; // PERMISSION_DENIED: does not have children color and size [ref setValue: @"foo"]; // PERMISSION DENIED: does not have child color [ref setValue: @{ @"size": @"foo" }]; // PERMISSION_DENIED: size is not a number [ref setValue: @{ @"size": @"foo", @"color": @"red" }]; // SUCCESS (assuming 'blue' appears in our colors list) [ref setValue: @{ @"size": @21, @"color": @"blue" }]; // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate [[ref child:@"size"] setValue: @99];
Swift
var ref = FIRDatabase.database().reference().child("widget") // PERMISSION_DENIED: does not have children color and size ref.setValue("foo") // PERMISSION DENIED: does not have child color ref.setValue(["size": "foo"]) // PERMISSION_DENIED: size is not a number ref.setValue(["size": "foo", "color": "red"]) // SUCCESS (assuming 'blue' appears in our colors list) ref.setValue(["size": 21, "color": "blue"]) // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
Java
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("widget"); // PERMISSION_DENIED: does not have children color and size ref.setValue("foo"); // PERMISSION DENIED: does not have child color ref.child("size").setValue(22); // PERMISSION_DENIED: size is not a number Map<String,Object> map = new HashMap<String, Object>(); map.put("size","foo"); map.put("color","red"); ref.setValue(map); // SUCCESS (assuming 'blue' appears in our colors list) map = new HashMap<String, Object>(); map.put("size", 21); map.put("color","blue"); ref.setValue(map); // If the record already exists and has a color, this will // succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) // will fail to validate ref.child("size").setValue(99);
REST
# PERMISSION_DENIED: does not have children color and size curl -X PUT -d 'foo' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION DENIED: does not have child color curl -X PUT -d '{"size": 22}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # PERMISSION_DENIED: size is not a number curl -X PUT -d '{"size": "foo", "color": "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # SUCCESS (assuming 'blue' appears in our colors list) curl -X PUT -d '{"size": 21, "color": "blue"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # If the record already exists and has a color, this will # succeed, otherwise it will fail since newData.hasChildren(['color', 'size']) # will fail to validate curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
لنلقِ نظرة الآن على البنية نفسها، ولكن باستخدام قواعد .write
بدلاً من .validate
:
{ "rules": { // this variant will NOT allow deleting records (since .write would be disallowed) "widget": { // a widget must have 'color' and 'size' in order to be written to this path ".write": "newData.hasChildren(['color', 'size'])", "size": { // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99" }, "color": { // the value of "color" must exist as a key in our mythical valid_colors/ index // BUT ONLY IF WE WRITE DIRECTLY TO COLOR ".write": "root.child('valid_colors/'+newData.val()).exists()" } } } }
في هذا النوع، سينجح أي من العمليات التالية:
JavaScript
var ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.set({size: 99999, color: 'red'}); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child('size').set(99);
Objective-C
Firebase *ref = [[Firebase alloc] initWithUrl:URL]; // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored [ref setValue: @{ @"size": @9999, @"color": @"red" }]; // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") [[ref childByAppendingPath:@"size"] setValue: @99];
Swift
var ref = Firebase(url:URL) // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored ref.setValue(["size": 9999, "color": "red"]) // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.childByAppendingPath("size").setValue(99)
Java
Firebase ref = new Firebase(URL + "/widget"); // ALLOWED? Even though size is invalid, widget has children color and size, // so write is allowed and the .write rule under color is ignored Map<String,Object> map = new HashMap<String, Object>(); map.put("size", 99999); map.put("color", "red"); ref.setValue(map); // ALLOWED? Works even if widget does not exist, allowing us to create a widget // which is invalid and does not have a valid color. // (allowed by the write rule under "color") ref.child("size").setValue(99);
REST
# ALLOWED? Even though size is invalid, widget has children color and size, # so write is allowed and the .write rule under color is ignored curl -X PUT -d '{size: 99999, color: "red"}' \ https://docs-examples.firebaseio.com/rest/securing-data/example.json # ALLOWED? Works even if widget does not exist, allowing us to create a widget # which is invalid and does not have a valid color. # (allowed by the write rule under "color") curl -X PUT -d '99' \ https://docs-examples.firebaseio.com/rest/securing-data/example/size.json
يوضّح هذا الجدول الاختلافات بين قواعد .write
و.validate
.
كما هو موضّح، يجب كتابة كل هذه القواعد باستخدام .validate
، مع استثناء محتمل للقاعدة newData.hasChildren()
، والذي يعتمد على ما إذا كان يجب السماح بعمليات الحذف.
القواعد المستندة إلى طلبات البحث
على الرغم من أنّه لا يمكنك استخدام القواعد كفلاتر، يمكنك حصر الوصول إلى مجموعات فرعية من البيانات باستخدام مَعلمات طلب البحث في قواعدك.
استخدِم تعبيرات query.
في قواعدك لمنح إذن القراءة أو الكتابة استنادًا إلى مَعلمات طلب البحث.
على سبيل المثال، تستخدم القاعدة التالية المستندة إلى طلب البحث قواعد الأمان المستندة إلى المستخدم
والقواعد المستندة إلى طلب البحث لحصر الوصول إلى البيانات في المجموعة baskets
على سلال التسوّق التي يملكها المستخدم النشط فقط:
"baskets": {
".read": "auth.uid !== null &&
query.orderByChild === 'owner' &&
query.equalTo === auth.uid" // restrict basket access to owner of basket
}
سينجح طلب البحث التالي، الذي يتضمّن مَعلمات طلب البحث في القاعدة:
db.ref("baskets").orderByChild("owner")
.equalTo(auth.currentUser.uid)
.on("value", cb) // Would succeed
ومع ذلك، ستتعذّر طلبات البحث التي لا تتضمّن المَعلمات في القاعدة مع ظهور الخطأ PermissionDenied
:
db.ref("baskets").on("value", cb) // Would fail with PermissionDenied
يمكنك أيضًا استخدام قواعد مستندة إلى طلبات البحث للحدّ من مقدار البيانات التي ينزّلها العميل من خلال عمليات القراءة.
على سبيل المثال، تقصر القاعدة التالية إذن القراءة على أول 1000 نتيجة من نتائج طلب البحث، وذلك حسب ترتيبها حسب الأولوية:
messages: {
".read": "query.orderByKey &&
query.limitToFirst <= 1000"
}
// Example queries:
db.ref("messages").on("value", cb) // Would fail with PermissionDenied
db.ref("messages").limitToFirst(1000)
.on("value", cb) // Would succeed (default order by key)
تتوفّر تعبيرات query.
التالية في "قواعد الأمان" في Realtime Database.
عبارات القواعد المستندة إلى طلبات البحث | ||
---|---|---|
التعبير | النوع | الوصف |
query.orderByKey query.orderByPriority query.orderByValue |
قيمة منطقية | صحيح لطلبات البحث التي يتم ترتيبها حسب المفتاح أو الأولوية أو القيمة ويتم عرض القيمة "خطأ" إذا لم تكن نقطة الإدخال داخله. |
query.orderByChild | string null |
استخدِم سلسلة لتمثيل المسار النسبي إلى عقدة فرعية. على سبيل المثال، query.orderByChild === "address/zip" . إذا لم يتم ترتيب الاستعلام حسب عقدة فرعية، تكون هذه القيمة فارغة.
|
query.startAt query.endAt query.equalTo |
string number boolean null |
تعرض هذه الطريقة حدود طلب البحث الذي يتم تنفيذه، أو تعرض قيمة فارغة إذا لم يتم ضبط أي حدود. |
query.limitToFirst query.limitToLast |
number null |
تعرض هذه الدالة الحدّ الأقصى لعدد النتائج التي يعرضها طلب البحث، أو تعرض قيمة فارغة إذا لم يتم ضبط حدّ أقصى. |
الخطوات التالية
بعد هذه المناقشة حول الشروط، ستكون لديك معرفة أكثر تفصيلاً بشأن Rules وستكون مستعدًا لما يلي:
تعرَّف على كيفية التعامل مع حالات الاستخدام الأساسية، وعلى سير العمل الخاص بتطوير Rules واختباره ونشره:
- تعرَّف على المجموعة الكاملة من Rules المتغيّرات المحدّدة مسبقًا التي يمكنك استخدامها لإنشاء الشروط.
- اكتب قواعد تتناول السيناريوهات الشائعة.
- يمكنك تعزيز معرفتك من خلال مراجعة الحالات التي يجب فيها رصد القواعد غير الآمنة وتجنُّبها.
- تعرَّف على "مجموعة أدوات المحاكاة المحلية" من Firebase وكيفية استخدامها لاختبار Rules.
- راجِع الطرق المتاحة لنشر Rules.
ميزات Rules الخاصة بـ Realtime Database: