استخدام الشروط في قواعد أمان قاعدة البيانات في الوقت الفعلي

يستند هذا الدليل إلى دليل التعرّف على لغة قواعد أمان Firebase الأساسية لعرض كيفية إضافة شروط إلى قواعد أمان "قاعدة بيانات Firebase الآنية الاستجابة".

الشرط هو الوحدة الأساسية لقواعد أمان "قاعدة بيانات الوقت الفعلي". العبارة الشرطية هي تعبير منطقي يحدّد ما إذا كان يجب السماح بعملية معيّنة أو رفضها. بالنسبة إلى القواعد الأساسية، يعمل استخدام القيم الثابتة true وfalse كأحد الشروط بشكل جيد جدًا. ولكنّ لغة قواعد أمان "قاعدة بيانات الوقت الفعلي" تمنحك طُرقًا لكتابة شروط أكثر تعقيدًا يمكنها:

  • التحقّق من مصادقة المستخدم
  • تقييم البيانات الحالية مقارنةً بالبيانات التي تم إرسالها حديثًا
  • الوصول إلى أجزاء مختلفة من قاعدة بياناتك ومقارنتها
  • التحقّق من صحة البيانات الواردة
  • استخدام بنية طلبات البحث الواردة في منطق الأمان

استخدام متغيّرات $ لتسجيل أجزاء المسار

يمكنك تسجيل أجزاء من المسار للقراءة أو الكتابة من خلال تعريف متغيّرات الالتقاط باستخدام البادئة $. يُستخدَم هذا العنصر كحرف بدل، ويخزّن قيمة هذا المفتاح لاستخدامها داخل شروط القواعد:

{
  "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، يُرجى الاطّلاع على مستندات ال reference.

يتم دمج Firebase Authentication مع Firebase Realtime Database للسماح لك بالتحكم في الوصول إلى data على أساس كل مستخدم باستخدام الشروط. بعد مصادقة المستخدم، سيتم تعبئة المتغيّر auth في قواعد أمان "قاعدة بيانات الوقت الفعلي" بمعلومات المستخدم. وتشمل هذه المعلومات المعرّف الفريد (uid) بالإضافة إلى بيانات الحساب المرتبطة، مثل رقم تعريف Facebook أو عنوان البريد الإلكتروني، وغيرها من المعلومات. في حال استخدام مقدّم مصادقة مخصّص، يمكنك إضافة حقولك الخاصة إلى الحمولة البرمجية لمصادقة المستخدم.

يوضّح هذا القسم كيفية دمج لغة قواعد أمان قاعدة بيانات Firebase الآنية الاستجابة مع معلومات المصادقة عن المستخدمين. من خلال الجمع بين هذين المفهومَين، يمكنك التحكّم في الوصول إلى البيانات استنادًا إلى هوية المستخدم.

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

راجِع هذا المثال الذي يسمح بعمليات الكتابة ما دامت قيمة node /allow_writes/ هي true، ولا تحتوي node/allow_writes/ الرئيسية على علامة 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
ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
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
ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
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);
جافا
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 هذا في استهداف "المقاطع الترويجية للتطبيقات".
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
ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
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)
جافا
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.

تعبيرات القواعد المستندة إلى طلبات البحث
التعبير النوع الوصف
query.orderByKey
query.orderByPriority
query.orderByValue
قيمة منطقية صحيح لطلبات البحث التي يتم ترتيبها حسب المفتاح أو الأولوية أو القيمة. خطأ بخلاف ذلك.
query.orderByChild سلسلة
null
استخدِم سلسلة لتمثيل المسار النسبي إلى عقدة فرعية. على سبيل المثال، query.orderByChild === "address/zip". إذا لم يكن الطلب مرتبًا حسب عقدة فرعية، تكون هذه القيمة فارغة.
query.startAt
query.endAt
query.equalTo
سلسلة
عدد
قيمة منطقية
null
يسترجع حدود طلب البحث الذي يتم تنفيذه، أو يعرض قيمة فارغة إذا لم يتم ضبط حدود.
query.limitToFirst
query.limitToLast
عدد
فارغة
يسترجع الحدّ الأقصى المسموح به لتنفيذ الطلب، أو يعرض قيمة فارغة إذا لم يكن هناك حدّ أقصى محدّد.

الخطوات التالية

بعد هذه المناقشة حول الشروط، أصبح لديك فهم عميق عن Rules وأصبحت مستعدًا لإجراء ما يلي:

تعرَّف على كيفية التعامل مع حالات الاستخدام الأساسية، وتعرَّف على سير العمل لتطوير Rules واختباره ونشره:

التعرّف على ميزات Rules الخاصة بـ Realtime Database: