استخدام الشروط في قواعد أمان Firebase Cloud Storage

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

الوحدة الأساسية لـ Cloud Storage Security Rules هي الشرط. العبارة الشرطية هي تعبير منطقي يحدّد ما إذا كان يجب السماح بإجراء معيّن أو رفضه. بالنسبة إلى القواعد الأساسية، يعمل استخدام القيم الثابتة true وfalse كشرط بشكل جيد جدًا. ولكنّ لغة Firebase Security Rules for Cloud Storage تمنحك طرقًا لكتابة شروط أكثر تعقيدًا يمكنها:

  • التحقّق من مصادقة المستخدم
  • التحقّق من صحة البيانات الواردة

المصادقة

Firebase Security Rules لنظام التشغيل Cloud Storage يدمج مع Firebase Authentication لتوفير مصادقة قوية تستند إلى المستخدمين في Cloud Storage. يتيح ذلك التحكّم الدقيق في الوصول استنادًا إلى مطالبات برمز Firebase Authentication.

عندما يُجري مستخدم تمّت مصادقة هويته طلبًا ضد Cloud Storage، يتمّ تعبئة المتغيّر request.auth بقيمة uid (request.auth.uid) الخاصة بالمستخدم بالإضافة إلى مطالب Firebase Authentication JWT (request.auth.token).

بالإضافة إلى ذلك، عند استخدام المصادقة المخصّصة، يتم عرض مطالبات إضافية في الحقل request.auth.token.

عندما يُجري مستخدم لم يتم مصادقة هويته طلبًا، يكون المتغيّر request.auth هو null.

باستخدام هذه البيانات، هناك عدة طرق شائعة لاستخدام المصادقة لتأمين الملفات:

  • علني: تجاهل request.auth
  • خاص ومصادق عليه: تأكَّد من أنّ request.auth ليس null
  • خاص بالمستخدم: تأكَّد من أنّ request.auth.uid يساوي مسارًا uid
  • المجموعة الخاصة: التحقّق من مطالبات الرمز المميّز المخصّص لمطابقة مطالبة محدّدة، أو قراءة البيانات الوصفية للملف لمعرفة ما إذا كان هناك حقل بيانات وصفية

علني

يمكن اعتبار أي قاعدة لا تأخذ سياق request.auth في الاعتبار قاعدة public، لأنّها لا تأخذ سياق مصادقة المستخدم في الاعتبار. يمكن أن تكون هذه القواعد مفيدة لعرض البيانات المتاحة للجميع، مثل مواد عرض الألعاب أو الملفات الصوتية أو المحتوى الثابت الآخر.

// Anyone to read a public image if the file is less than 100kB
// Anyone can upload a public file ending in '.txt'
match /public/{imageId} {
  allow read: if resource.size < 100 * 1024;
  allow write: if imageId.matches(".*\\.txt");
}

محتوى خاص تمّت مصادقته

في بعض الحالات، قد تحتاج إلى أن تكون البيانات مرئية لجميع المستخدمين الذين تم مصادقة هويتهم في تطبيقك، ولكن ليس للمستخدمين الذين لم يتم مصادقة هويتهم. بما أنّ المتغيّر request.auth يكون null لجميع المستخدمين الذين لم تتم مصادقة هويتهم، ما عليك سوى التحقّق مما إذا كان المتغيّر request.auth موجودًا لطلب المصادقة:

// Require authentication on all internal image reads
match /internal/{imageId} {
  allow read: if request.auth != null;
}

محتوى خاص بالمستخدم

إنّ الحالة الأكثر شيوعًا لاستخدام request.auth هي منح مستخدمين individual أذونات دقيقة على ملفاتهم، بدءًا من تحميل صور الملف الشخصي ووصولاً إلى قراءة المستندات الخاصة.

بما أنّ الملفات في Cloud Storage لها "مسار" كامل إلى الملف، كل ما يلزم للقيام بذلك هو إنشاء ملف يتحكّم فيه مستخدم، وهو عبارة عن قطعة من معلومات تحديد هوية مستخدم فريدة في بادئة اسم الملف (مثل uid للمستخدم) يمكن فحصها عند تقييم القاعدة:

// Only a user can upload their profile picture, but anyone can view it
match /users/{userId}/profilePicture.png {
  allow read;
  allow write: if request.auth.uid == userId;
}

المجموعة خاصة

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

  • إنشاء Firebase Authentication رمز مميّز مخصّص يحتوي على معلومات إضافية عن أحد أعضاء المجموعة (مثل معرّف المجموعة)
  • تضمين معلومات المجموعة (مثل رقم تعريف المجموعة أو قائمة uid المعتمَدين) في البيانات الوصفية للملف

بعد تخزين هذه البيانات في بيانات التعريف الخاصة بالرمز المميّز أو الملف، يمكن الإشارة إليها من داخل قاعدة:

// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
  allow read: if resource.metadata.owner == request.auth.token.groupId;
  allow write: if request.auth.token.groupId == groupId;
}

طلب التقييم

يتم تقييم عمليات التحميل والتنزيل وتغييرات البيانات الوصفية والحذف باستخدام ملف request الذي تم إرساله إلى Cloud Storage. بالإضافة إلى المعرّف الفريد للمستخدم وحمولة Firebase Authentication في عنصر request.auth كما هو موضّح أعلاه، يحتوي المتغيّر request على مسار الملف الذي يتم تنفيذ الطلب فيه والوقت الذي تم فيه استلام الطلب وقيمة resource الجديدة إذا كان الطلب عبارة عن عملية كتابة.

يحتوي عنصر request أيضًا على المعرّف الفريد للمستخدم وحمله Firebase Authentication في عنصر request.auth، وسيتم شرحه بمزيد من التفصيل في قسم الأمان المستنِد إلى المستخدم في المستندات.

في ما يلي قائمة كاملة بالسمات في عنصر request:

الموقع النوع الوصف
auth map<string, string> عندما يكون المستخدم مسجِّلاً الدخول، يقدّم uid، وهو المعرّف الفريد للمستخدم، token، وهو خريطة لمطالبات Firebase Authentication JWT. بخلاف ذلك، سيكون null.
params map<string, string> خريطة تحتوي على مَعلمات طلب البحث
path المسار path يمثّل المسار الذي يتم تنفيذ الطلب عليه
resource map<string, string> قيمة المورد الجديدة، لا تظهر إلا في طلبات write.
time الطابع الزمني طابع زمني يمثّل وقت الخادم الذي يتم فيه تقييم الطلب.

تقييم الموارد

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

Firebase Security Rules لـ Cloud Storage: يوفّر البيانات الوصفية للملف في resource كائن، الذي يحتوي على أزواج مفتاح/قيمة للبيانات الوصفية التي تظهر في Cloud Storage كائن. يمكن فحص هذه المواقع عند تلقّي طلبات read أو write لضمان سلامة البيانات.

في طلبات write (مثل عمليات التحميل وتعديل البيانات الوصفية وعمليات الحذف)، بالإضافة إلى عنصر resource الذي يحتوي على البيانات الوصفية للملف الذي يوجد حاليًا في مسار الطلب، يمكنك أيضًا استخدام request.resource الذي يحتوي على مجموعة فرعية من البيانات الوصفية للملف التي سيتم كتابتها إذا كان الإذن بالكتابة مسموحًا به. يمكنك استخدام هاتين القيمتَين لضمان تكامل البيانات أو فرض قيود التطبيق، مثل نوع الملف أو حجمه.

في ما يلي قائمة كاملة بالسمات في عنصر resource:

الموقع النوع الوصف
name السلسلة الاسم الكامل للعنصر
bucket السلسلة اسم الحزمة التي يقع فيها هذا العنصر
generation int Google Cloud Storage إنشاء العنصر لهذا العنصر.
metageneration int Google Cloud Storage الجيل التعريفي للكائن لهذا الكائن.
size int حجم العنصر بالبايت
timeCreated الطابع الزمني طابع زمني يمثّل وقت إنشاء عنصر
updated الطابع الزمني طابع زمني يمثّل وقت آخر تعديل لعنصر
md5Hash السلسلة تجزئة MD5 للعنصر
crc32c السلسلة تجزئة crc32c للعنصر
etag السلسلة علامة etag المرتبطة بهذا العنصر
contentDisposition السلسلة حالة المحتوى المرتبطة بهذا العنصر
contentEncoding السلسلة ترميز المحتوى المرتبط بهذا العنصر
contentLanguage السلسلة لغة المحتوى المرتبطة بهذا العنصر.
contentType السلسلة نوع المحتوى المرتبط بهذا العنصر
metadata map<string, string> أزواج مفتاح/قيمة للبيانات الوصفية المخصّصة الإضافية التي يحدّدها المطوّر

يحتوي request.resource على كل هذه القيم باستثناء generation، metageneration، etag، timeCreated، وupdated.

تحسين الأداء باستخدام Cloud Firestore

يمكنك الوصول إلى المستندات في Cloud Firestore لتقييم معايير التفويض الأخرى.

باستخدام الدالتَين firestore.get() وfirestore.exists()، يمكن لقواعد الأمان تقييم الطلبات الواردة مقارنةً بالمستندات في Cloud Firestore. تتوقع كلتا الدالتَين firestore.get() وfirestore.exists() مسارات مستندات محدّدة بالكامل. عند استخدام المتغيّرات لإنشاء مسارات لسمتي firestore.get() وfirestore.exists()، عليك إزالة ترميز المتغيّرات بشكل صريح باستخدام بنية $(variable).

في المثال أدناه، نرى قاعدة تحدّ من إذن الوصول للقراءة إلى الملفات على صعيد المستخدمين الذين ينتمون إلى أندية معيّنة.

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{club}/files/{fileId} {
      allow read: if club in
        firestore.get(/databases/(default)/documents/users/$(request.auth.id)).memberships
    }
  }
}
في المثال التالي، يمكن لأصدقاء المستخدم فقط الاطّلاع على صوره.
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{fileId} {
      allow read: if
        firestore.exists(/databases/(default)/documents/users/$(userId)/friends/$(request.auth.id))
    }
  }
}

بعد إنشاء أول Cloud Storage Security Rules وحفظه باستخدام Cloud Firestore هذه الدوال، سيُطلب منك في وحدة تحكّم Firebase أو Firebase CLI تفعيل الأذونات لربط المنتجَين.

يمكنك إيقاف الميزة من خلال إزالة دور في "إدارة الهوية وإمكانية الوصول"، كما هو موضّح في مقالة إدارة ونشر Firebase Security Rules.

التحقّق من صحة البيانات

يمكن أيضًا استخدام Firebase Security Rules لـ Cloud Storage للتحقّق من صحة البيانات، بما في ذلك التحقّق من اسم الملف ومساره بالإضافة إلى سمات البيانات الوصفية للملف، مثل contentType وsize.

service firebase.storage {
  match /b/{bucket}/o {
    match /images/{imageId} {
      // Only allow uploads of any image file that's less than 5MB
      allow write: if request.resource.size < 5 * 1024 * 1024
                   && request.resource.contentType.matches('image/.*');
    }
  }
}

الدوال المخصّصة

مع زيادة تعقيد Firebase Security Rules، قد تحتاج إلى تضمين مجموعات من الشروط في دوال يمكنك إعادة استخدامها في قواعدك. تسمح قواعد الأمان باستخدام الدوال المخصّصة. يشبه أسلوب كتابة الدوالّ المخصّصة إلى حدٍّ ما أسلوب كتابة JavaScript، ولكنّ دوالّ Firebase Security Rules مكتوبة بلغة خاصة بالمجال تفرض بعض القيود المهمة:

  • يمكن أن تحتوي الدوالّ على عبارة return واحدة فقط. ولا يمكن أن تحتوي على أي منطق إضافي. على سبيل المثال، لا يمكنهم تنفيذ الحلقات أو الاتصال بخدمات خارجية.
  • يمكن للدوالّ الوصول تلقائيًا إلى الدوالّ والمتغيّرات من النطاق الذي تم تعريفها فيه. على سبيل المثال، يمكن لدالة محدّدة ضمن نطاق service firebase.storage الوصول إلى المتغيّر resource، والدوالّ المدمَجة مثل get() وexists() لـ Cloud Firestore فقط.
  • يمكن أن تستدعي الدوالّ دوالّ أخرى، ولكن لا يمكنها التكرار. يقتصر إجمالي عمق تسلسل استدعاء الدوال البرمجية على 10.
  • في الإصدار rules2، يمكن للدوالّ تحديد المتغيّرات باستخدام الكلمة الأساسية let. يمكن أن تحتوي الدوال على أي عدد من عمليات الربط باستخدام let، ولكن يجب أن تنتهي بعبارة ناتجة.

يتم تعريف الدالة باستخدام الكلمة الرئيسية function وتستخدِم وسيطات صفرية أو أكثر. على سبيل المثال، قد تحتاج إلى دمج نوعَي الشروط المستخدَمَين في الأمثلة أعلاه في دالة واحدة:

service firebase.storage {
  match /b/{bucket}/o {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }
    match /images/{imageId} {
      allow read, write: if signedInOrPublic();
    }
    match /mp3s/{mp3Ids} {
      allow read: if signedInOrPublic();
    }
  }
}

يؤدي استخدام الدوالّ في Firebase Security Rules إلى تسهيل صيانتها مع زيادة صعوبة قواعدك.

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

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

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