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

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

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

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

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

تتكوّن قواعد أمان قاعدة بيانات الوقت الفعلي من تعبيرات تشبه 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
ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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 هذا في هدف App Clip.
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 هذا في هدف App Clip.
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
[[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
    // SUCCESS!
}];
Swift
ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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" التي تتناولها.