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

يستند هذا الدليل إلى دليل التعرّف على دليل اللغة الأساسية لقواعد أمان 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، سيحتوي المتغيّر request.auth على معلومات المصادقة للعميل الذي يطلب البيانات. لمزيد من المعلومات حول request.auth، يُرجى الاطّلاع على المرجع المستندات.

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

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

متغيّر auth

يكون متغيّر auth المحدَّد مسبقًا في القواعد فارغًا قبل إجراء المصادقة.

بعد مصادقة المستخدم من خلال مصادقة Firebase سوف يحتوي على السمات التالية:

مزوِّد الخدمة طريقة المصادقة المستخدمة ("password"، و"مجهول"، و"facebook"، و"github"، و"google"، أو "Twitter").
رقم uid رقم تعريف مستخدم فريد يضمن أن يكون فريدًا على مستوى جميع مقدّمي الخدمة
رمز مميز محتوى الرمز المميّز لمعرّف مصادقة Firebase. الاطّلاع على المرجع وثائق يمكنك 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"
      }
    }
  }
}

تنظيم قاعدة البيانات لدعم شروط المصادقة

من المفيد عادةً تنظيم قاعدة البيانات الخاصة بك بطريقة تجعل الكتابة القواعد أسهل. أحد الأنماط الشائعة لتخزين بيانات المستخدم في قاعدة بيانات الوقت الفعلي هو لتخزين جميع المستخدمين في عقدة users واحدة هي العناصر التابعة لها قيم uid لكل مستخدم. إذا أردت تقييد الوصول إلى هذه البيانات بحيث يمكن للمستخدم الذي سجّل الدخول فقط الاطّلاع على بياناته الخاصة، شيئًا من هذا القبيل.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

التعامل مع مطالبات المصادقة المخصصة

بالنسبة إلى التطبيقات التي تتطلب تحكُّمًا مخصّصًا في الوصول إلى مستخدمين مختلفين، تُفترَض مصادقة Firebase تسمح للمطوّرين بتحديد مطالبات بملكية أحد مستخدمي 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، الوصول إلى أي مسار كما سيكون قبل حدث الكتابة أو بعده.

ضع في الاعتبار هذا المثال، الذي يسمح بعمليات الكتابة طالما أن قيمة العقدة /allow_writes/ هي true، ولا تحتوي العقدة الرئيسية على تم ضبط علم 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 هذا في هدف App Clip.
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 هذا في هدف App Clip.
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 هذا في هدف App Clip.
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 هذا في هدف App Clip.
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

يمكنك أيضًا استخدام قواعد مستندة إلى طلبات البحث للحدّ من كمية البيانات التي ينزِّلها العميل. من خلال عمليات القراءة.

على سبيل المثال، تفرض القاعدة التالية قيودًا على إمكانية الوصول للقراءة فقط لأول 1, 000 مستخدم. نتائج استعلام، حسب الترتيب حسب الأولوية:

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. التالية في "قواعد أمان قاعدة البيانات في الوقت الفعلي".

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

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

بعد هذه المناقشة للشروط، تكون لديك فكرة أكثر تعقيدًا فهم القواعد وهم مستعدون لما يلي:

تعلُّم كيفية التعامل مع حالات الاستخدام الأساسية وتعلُّم سير العمل لتطوير اختبار القواعد ونشرها:

تعرَّف على ميزات "القواعد" الخاصة بقاعدة البيانات في الوقت الفعلي: