التعرف على البنية الأساسية للغة قواعد أمان قاعدة البيانات في الوقت الفعلي

تتيح لك "قواعد الأمان" في "قاعدة بيانات Firebase في الوقت الفعلي" التحكّم في الوصول إلى البيانات المخزّنة في قاعدة البيانات. يتيح لك تركيب القواعد المرن إنشاء قواعد تطابق أي شيء، بدءًا من جميع عمليات الكتابة إلى قاعدة البيانات وصولاً إلى العمليات على العُقد الفردية.

قواعد الأمان في Realtime Database هي إعدادات توضيحية لقاعدة البيانات. وهذا يعني أنّ القواعد يتم تحديدها بشكل منفصل عن منطق المنتج. ويوفّر ذلك عددًا من المزايا، منها أنّ العملاء ليسوا مسؤولين عن فرض الأمان، ولن تؤدي عمليات التنفيذ التي تتضمّن أخطاء إلى تعريض بياناتك للخطر، والأهم من ذلك أنّه لا حاجة إلى وسيط، مثل خادم، لحماية البيانات من العالم.

يوضّح هذا الموضوع البنية الأساسية لقواعد الأمان في Realtime Database المستخدَمة لإنشاء مجموعات قواعد كاملة.

تنظيم قواعد الأمان

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

متغيّرات التقاط أحرف البدل

تشير جميع عبارات القواعد إلى عُقد. يمكن أن يشير البيان إلى عقدة معيّنة أو يستخدم $ حرف بدل لالتقاط المتغيرات للإشارة إلى مجموعات من العُقد على مستوى التسلسل الهرمي. استخدِم متغيرات الالتقاط هذه لتخزين قيمة مفاتيح العُقد لاستخدامها داخل عبارات القواعد اللاحقة. تتيح لك هذه الطريقة كتابة 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
ملاحظة: لا يتوفّر منتج Firebase هذا على هدف "مقتطف التطبيق".
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
ملاحظة: لا يتوفّر منتج Firebase هذا على هدف "مقتطف التطبيق".
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، يمكننا أن نرى أنّه تم رفض عملية القراءة لأنّه لم تسمح أي قاعدة قراءة بالوصول إلى المسار /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
ملاحظة: لا يتوفّر منتج Firebase هذا على هدف "مقتطف التطبيق".
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
ملاحظة: لا يتوفّر منتج Firebase هذا على هدف "مقتطف التطبيق".
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 في الوقت الفعلي" من خلال:

  • تعرَّف على المفهوم الرئيسي التالي في لغة Rules، وهو الشروط الديناميكية، التي تتيح للغة Rules التحقّق من تفويض المستخدم، ومقارنة البيانات الحالية والواردة، والتحقّق من صحة البيانات الواردة، والتحقّق من بنية طلبات البحث الواردة من العميل، وغير ذلك.

  • راجِع حالات الاستخدام النموذجية للأمان وتعريفات "قواعد الأمان في Firebase" التي تتناولها.