تسمح لك "قواعد الأمان" في قاعدة بيانات Firebase في الوقت الفعلي بالتحكّم في الوصول إلى البيانات المخزّنة في قاعدة بياناتك. يسمح لك بناء جملة القواعد المرن بإنشاء قواعد تطابِق أي شيء، بدءًا من جميع عمليات الكتابة في قاعدة بياناتك وصولاً إلى العمليات على العُقد الفردية.
"قواعد الأمان" في قاعدة بيانات الوقت الفعلي هي إعداد تصريحي لقاعدة بياناتك. وهذا يعني أنّ القواعد يتم تحديدها بشكل منفصل عن منطق المنتج. ويوفّر ذلك عددًا من المزايا: لا يكون العملاء مسؤولين عن فرض الأمان، ولن تؤدي عمليات التنفيذ التي تتضمّن أخطاء إلى تعريض بياناتك للخطر، والأهم من ذلك أنّه ما مِن حاجة إلى وسيط، مثل خادم، لحماية البيانات من العالم.
يصف هذا الموضوع بناء الجملة الأساسي وبنية "قواعد الأمان" في Realtime Database المستخدَمة لإنشاء مجموعات قواعد كاملة.
تنظيم "قواعد الأمان"
تتألّف "قواعد الأمان" في قاعدة بيانات الوقت الفعلي من تعابير تشبه JavaScript مضمّنة في مستند JSON. يجب أن يتّبع هيكل قواعدك هيكل البيانات المخزّنة في قاعدة بياناتك.
تُحدِّد القواعد الأساسية مجموعة من العُقد التي يجب تأمينها، و طُرق الوصول (مثل القراءة والكتابة) المعنيّة، و الشروط التي يتم بموجبها السماح بالوصول أو رفضه.
في الأمثلة التالية، ستكون الشروط عبارة عن عبارتَي true وfalse بسيطتَين، ولكن في الموضوع التالي سنشرح طُرقًا أكثر ديناميكية للتعبير عن الشروط.
على سبيل المثال، إذا كنّا نحاول تأمين 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 | تحدّد شكل القيمة المنسّقة بشكل صحيح، وما إذا كانت تتضمّن سمات فرعية ونوع البيانات. |
متغيّرات التقاط الأحرف البَديلة
تشير جميع عبارات القواعد إلى عُقد. يمكن أن تشير العبارة إلى عقدة معيّنة أو تستخدم متغيّرات التقاط الأحرف البَديلة $ للإشارة إلى مجموعات من العُقد على مستوى معيّن من التسلسل الهرمي. استخدِم متغيّرات الالتقاط هذه لتخزين قيمة مفاتيح العُقد لاستخدامها داخل عبارات القواعد اللاحقة. تتيح لك هذه التقنية كتابة
شروطSecurity 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 })
Java
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 console، يمكننا أن نرى أنّه
تم رفض عملية القراءة لأنّه ما مِن قاعدة قراءة سمحت بالوصول إلى
/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! })
Java
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 في الوقت الفعلي:
تعرَّف على المفهوم الرئيسي التالي للغة Security Rules، وهو الشروط الديناميكية ، التي تتيح لـ Security Rules التحقّق من إذن المستخدم ، ومقارنة البيانات الحالية والواردة، والتحقّق من صحة البيانات الواردة، والتحقّق من بنية طلبات البحث الواردة من العميل، والمزيد.
راجِع حالات الاستخدام النموذجية للأمان وتعريفات "قواعد الأمان" في Firebase التي تعالجها.