استخدام الشروط في قواعد أمان 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، ولكن يجب أن تنتهي بعبارة return.

يتم تعريف الدالة باستخدام الكلمة الرئيسية 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 إلى تسهيل صيانتها مع زيادة صعوبة قواعدك.

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

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

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