تتيح لك قواعد أمان "قاعدة بيانات Firebase في الوقت الفعلي" التحكّم في الوصول إلى البيانات المخزّنة في قاعدة بياناتك. تسمح لك بنية القواعد المرنة بإنشاء قواعد تتطابق مع أي شيء، بدءًا من جميع عمليات الكتابة إلى قاعدة بياناتك ووصولاً إلى العمليات على العقد الفردية.
قواعد أمان "قاعدة بيانات Realtime" هي إعدادات تعريفية لقاعدة بياناتك. وهذا يعني أنّه يتمّ تحديد القواعد بشكل منفصل عن منطق المنتج. لهذه الميزة العديد من المزايا: لا تتحمّل التطبيقات العميلة مسؤولية فرض الأمان، ولن تؤدي عمليات التنفيذ التي تتضمّن أخطاء إلى تعريض بياناتك للخطر، والأهم من ذلك، ليس هناك حاجة إلى وسيط مثل الخادم لحماية البيانات من العالم الخارجي.
يوضّح هذا الموضوع البنية الأساسية لقواعد أمان "قاعدة بيانات الوقت الفعلي" والصيغة الأساسية المستخدَمة لإنشاء قواعد قواعد كاملة.
تنظيم قواعد الأمان
تتكوّن قواعد أمان "قاعدة بيانات الوقت الفعلي" من تعبيرات مشابهة لـ JavaScript مضمّنة في مستند JSON. يجب أن تتّبع بنية قواعدك بنية البيانات التي تخزّنها في قاعدة بياناتك.
تحدِّد القواعد الأساسية مجموعة من العقد التي يجب تأمينها، وطرق الوصول (مثل القراءة
والكتابة) المُستخدَمة، والشروط التي يتم بموجبها السماح بالوصول أو رفضه.
في الأمثلة التالية، ستكون الشروط عبارة عن عبارات true
و
false
بسيطة، ولكن في الموضوع التالي سنتناول طرقًا أكثر ديناميكية ل expressed conditions.
على سبيل المثال، إذا كنا نحاول تأمين child_node
ضمن parent_node
،
يكون بناء الجملة العام الذي يجب اتّباعه على النحو التالي:
{ "rules": { "parent_node": { "child_node": { ".read": <condition>, ".write": <condition>, ".validate": <condition>, } } } }
لنطبّق هذا النمط. على سبيل المثال، لنفترض أنّك تتتبّع قائمة بالرسائل وتتوفّر لديك بيانات بالشكل التالي:
{ "messages": { "message0": { "content": "Hello", "timestamp": 1405704370369 }, "message1": { "content": "Goodbye", "timestamp": 1405704395231 }, ... } }
يجب أن تكون قواعدك منظَّمة بطريقة مشابهة. في ما يلي مجموعة من قواعد الأمان للقراءة فقط التي قد تكون منطقية لهيكل البيانات هذا. يوضّح هذا المثال كيفية تحديد عقد قاعدة البيانات التي تنطبق عليها القواعد و شروط تقييم القواعد في تلك العقد.
{ "rules": { // For requests to access the 'messages' node... "messages": { // ...and the individual wildcarded 'message' nodes beneath // (we'll cover wildcarding variables more a bit later).... "$message": { // For each message, allow a read operation if <condition>. In this // case, we specify our condition as "true", so read access is always granted. ".read": "true", // For read-only behavior, we specify that for write operations, our // condition is false. ".write": "false" } } } }
عمليات القواعد الأساسية
هناك ثلاثة أنواع من القواعد لفرض الأمان استنادًا إلى نوع
العملية التي يتم إجراؤها على البيانات: .write
و.read
و.validate
. في ما يلي
ملخّص سريع عن أغراضها:
أنواع القواعد | |
---|---|
.read | يوضّح ما إذا كان يُسمح للمستخدمين بقراءة البيانات ومتى يُسمح لهم بذلك. |
.write | يصف ما إذا كان يُسمح بكتابة البيانات وحالات السماح بذلك. |
.validate | تحدّد هذه السمة الشكل الذي ستظهر به القيمة المنسّقة بشكل صحيح، وما إذا كانت تحتوي على سمات فرعية، ونوع البيانات. |
متغيّرات تسجيل أحرف البدل
تشير جميع عبارات القواعد إلى العقد. يمكن أن يشير البيان إلى ملف شخصي
معيّن أو يستخدم $
متغيّرات الالتقاط للأحرف البدل للإشارة إلى مجموعات من الملفات الشخصية في
مستوى من التسلسل الهرمي. استخدِم متغيّرات الالتقاط هذه لتخزين قيمة مفاتيح
العقد لاستخدامها داخل عبارات القواعد اللاحقة. تتيح لك هذه التقنية كتابة
Rules شروط أكثر تعقيدًا، وهو ما سنتناوله بمزيد من التفصيل
في الموضوع التالي.
{ "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 } } } }
تسلسل قواعد القراءة والكتابة
تعمل قواعد .read
و.write
من الأعلى إلى الأسفل، مع إلغاء القواعد السطحية
للقواعد الأكثر عمقًا. إذا كانت القاعدة تمنح أذونات قراءة أو كتابة في مسار معيّن، ستمنح أيضًا إذن الوصول إلى
جميع العقد الفرعية ضمنها. راجِع البنية التالية:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { /* ignored, since read was allowed already */ ".read": false } } } }
تسمح بنية الأمان هذه بقراءة /bar/
متى كانت
/foo/
تحتوي على عنصر فرعي baz
بالقيمة true
.
لا تُحدث قاعدة ".read": false
ضمن /foo/bar/
أثرًا
هنا، لأنّه لا يمكن إلغاء الإذن بالوصول من خلال مسار فرعي.
على الرغم من أنّه قد لا يبدو واضحًا على الفور، إلا أنّ هذا جزء قوي من لغة القواعد ويسمح بتنفيذ امتيازات وصول معقّدة جدًا بأقل جهد. سيتم توضيح ذلك عند التطرق إلى الأمان المستنِد إلى المستخدم لاحقًا في هذا الدليل.
يُرجى العلم أنّ قواعد .validate
لا يتم تطبيقها بشكل تسلسلي. يجب استيفاء جميع قواعد التحقّق
على جميع مستويات التدرّج الهرمي للسماح بالكتابة.
القواعد ليست فلاتر
يتم تطبيق القواعد بطريقة موحّدة. وهذا يعني أنّه يتم رفض عملية قراءة أو كتابة على الفور إذا لم تكن هناك قاعدة في ذلك الموقع الجغرافي أو في الموقع الجغرافي الرئيسي تمنح الإذن بالوصول. حتى إذا كان بالإمكان الوصول إلى كل مسار فرعي متأثر، لن تتمكّن من قراءة البيانات في الموقع الجغرافي الرئيسي على الإطلاق. راجِع البنية التالية:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
بدون فهم أنّه يتم تقييم القواعد بشكل ذري، قد يبدو أنّه عند جلب مسار /records/
، سيتم عرض rec1
وليس rec2
. ولكن النتيجة الفعلية هي خطأ:
JavaScript
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
جافا
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
REST
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
بما أنّ عملية القراءة في /records/
هي عملية ذرية، ولا تتوفّر
قاعدة قراءة تمنح إذن الوصول إلى كل البيانات ضمن /records/
،
سيؤدي ذلك إلى ظهور خطأ PERMISSION_DENIED
. إذا قيّمنا هذه
القاعدة في محاكي الأمان في وحدة تحكّم Firebase، يمكننا ملاحظة أنّه
تم رفض عملية القراءة لأنّه لم تسمح أي قاعدة قراءة بالوصول إلى مسار
/records/
. يُرجى العلم أنّه لم يتم تقييم قاعدة rec1
مطلقًا لأنّها لم تكن في المسار الذي طلبناه. لجلب
rec1
، يجب الوصول إليه مباشرةً:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
Swift
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
جافا
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
REST
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
البيانات المتداخلة
من الممكن أن تنطبق أكثر من قاعدة واحدة على عقدة. في
الحالة التي تحدِّد فيها تعبيرات قواعد متعدّدة عقدة، يتم
رفض طريقة الوصول إذا كان أي من الشروط false
:
{ "rules": { "messages": { // A rule expression that applies to all nodes in the 'messages' node "$message": { ".read": "true", ".write": "true" }, // A second rule expression applying specifically to the 'message1` node "message1": { ".read": "false", ".write": "false" } } } }
في المثال أعلاه، سيتم منع قراءة العقدة message1
لأنّ القاعدة الثانية هي false
دائمًا، على الرغم من أنّ القاعدة
الأولى هي true
دائمًا.
الخطوات التالية
يمكنك التعمّق في فهم قواعد أمان قاعدة بيانات Firebase في الوقت الفعلي:
تعرَّف على المفهوم الرئيسي التالي للغة Rules، وهو الشروط الديناميكية، التي تتيح لتطبيقك التحقّق من تفويض المستخدِم، ومقارنة البيانات الحالية والواردة، والتحقّق من صحة البيانات الواردة، والتحقّق من بنية طلبات البحث الواردة من العميل، وغير ذلك.Rules
راجِع حالات استخدام الأمان الشائعة وتعريفات قواعد أمان Firebase التي تعالج هذه الحالات.