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

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

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

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

المصادقة

تتكامل قواعد أمان Firebase المخصصة لخدمة Cloud Storage مع مصادقة Firebase لتوفير مصادقة فعّالة مستندة إلى المستخدم لخدمة Cloud Storage. ويتيح ذلك التحكم الدقيق في الوصول بناءً على مطالبات الرمز المميز لمصادقة Firebase.

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

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

بما أنّ الملفات في 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 يحتوي على معلومات إضافية حول عضو في المجموعة (مثل رقم تعريف المجموعة)
  • يمكنك تضمين معلومات المجموعة (مثل رقم تعريف المجموعة أو قائمة 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 في العنصر request.auth كما هو موضّح أعلاه، يحتوي المتغيّر request على مسار الملف الذي يتم فيه تنفيذ الطلب والوقت الذي يتم فيه استلام الطلب وقيمة resource الجديدة إذا كان الطلب مكتوبًا.

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

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

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

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

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

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

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

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

الموقع Type الوصف
name سلسلة الاسم الكامل للعنصر
bucket سلسلة اسم الحزمة التي يتوفّر فيها هذا العنصر
generation int عملية إنشاء العناصر في Google Cloud Storage لهذا العنصر.
metageneration int إنشاء البيانات الوصفية للكائن Google Cloud Storage لهذا العنصر.
size int حجم العنصر بالبايت.
timeCreated الطابع الزمني طابع زمني يمثّل وقت إنشاء العنصر
updated الطابع الزمني طابع زمني يمثّل آخر مرة تم فيها تعديل العنصر
md5Hash سلسلة تجزئة MD5 للكائن.
crc32c سلسلة تجزئة crc32c للكائن.
etag سلسلة العلامة الإلكترونية المرتبطة بهذا الكائن.
contentDisposition سلسلة ترتيب المحتوى المرتبط بهذا العنصر
contentEncoding سلسلة ترميز المحتوى المرتبط بهذا العنصر.
contentLanguage سلسلة لغة المحتوى المرتبطة بهذا العنصر
contentType سلسلة نوع المحتوى المرتبط بهذا العنصر
metadata خريطة<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 الأولى التي تستخدم وظائف Cloud Firestore هذه، سيُطلب منك في وحدة تحكُّم Firebase أو واجهة سطر الأوامر في Firebase تفعيل الأذونات لربط المنتجين.

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

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

يمكن أيضًا استخدام قواعد أمان Firebase المخصصة لخدمة 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 أصبحت أكثر تعقيدًا، فقد تحتاج إلى التفاف مجموعات من الشروط في الدوال التي يمكنك إعادة استخدامها عبر مجموعة القواعد. تدعم قواعد الأمان الوظائف المخصصة. تشبه بنية الدوال المخصصة إلى حد ما JavaScript، ولكن دوال قواعد أمان Firebase مكتوبة بلغة محددة للنطاق هناك بعض القيود المهمة:

  • يمكن أن تحتوي الدوال على عبارة return واحدة فقط. ولا يمكن أن تحتوي على أي منطق إضافي. على سبيل المثال، لا يمكنها تنفيذ التكرارات الحلقية أو استدعاء الخدمات الخارجية.
  • يمكن للدوال الوصول تلقائيًا إلى الدوال والمتغيرات من النطاق الذي تم تحديدها فيه. على سبيل المثال، يمكن لدالة محدّدة ضمن النطاق service firebase.storage الوصول إلى المتغيّر resource، وفي Cloud Firestore فقط، يمكن للدوال المضمّنة مثل get() وexists().
  • قد تستدعي الدوال دوال أخرى ولكنها قد لا تتكرر. يقتصر إجمالي عمق تكديس الاستدعاءات على 10.
  • في الإصدار rules2، يمكن أن تحدد الدوال المتغيّرات باستخدام الكلمة الرئيسية 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" إلى زيادة إمكانية صيانتها مع تزايد تعقيد القواعد.

الخطوات اللاحقة

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

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