قواعد الأمان في Firebase — Firebase
قواعد الأمان في Firebase
لأن التطبيق يتصل بـ Firestore و Storage مباشرة من جهاز المستخدم، فقواعد الأمان هي خادمك الوحيد: كل قراءة وكتابة تُقيَّم مقابلها. قواعد خاطئة = بيانات كل المستخدمين مكشوفة أو قابلة للتعديل.
البنية
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if <شرط>;
allow create, update, delete: if <شرط>;
}
}
}- الرفض افتراضي: ما لم تسمح به قاعدة صراحةً فهو مرفوض.
- read = get + list، و write = create + update + delete — افصلها لتحكم أدق.
- القواعد ليست فلاتر: استعلام قد يعيد وثيقة غير مسموحة يُرفض بالكامل. يجب أن يتضمن الاستعلام نفس الشرط (where("authorId", "==", uid)).
المتغيرات المتاحة
| المتغير | المعنى |
|---|---|
| request.auth | المستخدم (null إن لم يكن مسجلاً)؛ request.auth.uid و request.auth.token |
| resource.data | الوثيقة الحالية في قاعدة البيانات |
| request.resource.data | الوثيقة بعد الكتابة المطلوبة |
الأنماط الأساسية
// الملكية
allow read, update, delete: if request.auth != null && request.auth.uid == resource.data.authorId;
// الإنشاء باسم نفسك فقط
allow create: if request.auth != null && request.resource.data.authorId == request.auth.uid;
// التحقق من البيانات
allow create: if request.resource.data.title is string
&& request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 100
&& request.resource.data.keys().hasOnly(['title', 'body', 'authorId', 'createdAt']);تصعيد الصلاحيات: أخطر الأخطاء
القواعد السابقة في هذا الدرس كانت تسمح بـ:
- إنشاء وثيقة أي مستخدم (allow create: if isAuthenticated() على users/{userId}) — فينشئ مهاجم ملفاً باسم غيره.
- تعديل الحقول الحساسة في وثيقته: مستخدم يضيف role: "admin" لنفسه.
- نقل ملكية منشور بتغيير authorId عند التحديث.
الإصلاح في المثال الكامل: الإنشاء للمالك فقط، ومنع الحقول الحساسة بـ diff(...).affectedKeys().hasAny([...])، والتأكد أن authorId لا يتغير. والأدوار تُحفظ في Custom Claims (request.auth.token.admin == true) لا في وثيقة يعدّلها المستخدم.
Storage
نفس الفكرة مع request.resource.size و request.resource.contentType. ضع حداً للحجم والنوع حتى في المسارات "العامة".
اختبر قواعدك
- محاكي Firebase (firebase emulators:start) ومكتبة @firebase/rules-unit-testing: اكتب اختبارات "يجب أن يُرفض" لكل محاولة مهاجم.
- Rules Playground في لوحة التحكم لتجربة طلب محدد.
- لا تنشر allow read, write: if true أبداً، ولا تترك "وضع الاختبار" (الذي يفتح كل شيء لمدة 30 يوماً) في مشروع حقيقي.
تمرين
اكتب اختبارات بالمحاكي تتأكد أن: المستخدم أ لا يقرأ وثيقة المستخدم ب، ولا يستطيع إضافة role لنفسه، ولا يستطيع تغيير authorId لمنشوره، ولا رفع ملف 10MB.
// ===== قواعد Firestore =====
// firebase.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// دالة مساعدة: هل المستخدم مسجل؟
function isAuthenticated() {
return request.auth != null;
}
// دالة مساعدة: هل المستخدم هو المالك؟
function isOwner(userId) {
return request.auth.uid == userId;
}
// ===== قواعد المستخدمين =====
match /users/{userId} {
// القراءة: للمالك فقط
allow read: if isOwner(userId);
// الإنشاء: المستخدم ينشئ وثيقته هو فقط، دون منح نفسه دوراً
allow create: if isOwner(userId) && !("role" in request.resource.data);
// التحديث: للمالك فقط، ولا يستطيع تغيير دوره
allow update: if isOwner(userId)
&& !request.resource.data.diff(resource.data).affectedKeys().hasAny(["role"]);
// الحذف: ممنوع
allow delete: if false;
}
// ===== قواعد المنشورات =====
match /posts/{postId} {
// القراءة: للجميع إذا منشور عام
allow read: if resource.data.public == true
|| isOwner(resource.data.authorId);
// الإنشاء: للمسجلين مع بيانات صحيحة
allow create: if isAuthenticated()
&& request.resource.data.authorId == request.auth.uid
&& request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 100;
// التحديث: للمالك فقط، ولا يُنقل المنشور لمؤلف آخر
allow update: if isOwner(resource.data.authorId)
&& request.resource.data.authorId == resource.data.authorId;
// الحذف: للمالك فقط
allow delete: if isOwner(resource.data.authorId);
}
}
}
// ===== قواعد Storage =====
// storage.rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// ملفات المستخدمين
match /users/{userId}/{allPaths=**} {
// القراءة: للجميع
allow read;
// الكتابة: للمالك فقط
allow write: if request.auth.uid == userId
&& request.resource.size < 5 * 1024 * 1024 // 5MB max
&& request.resource.contentType.matches('image/.*');
}
// ملفات عامة
match /public/{allPaths=**} {
allow read;
// حتى الملفات العامة: حجم ونوع محددان
allow write: if request.auth != null
&& request.resource.size < 2 * 1024 * 1024
&& request.resource.contentType.matches("image/.*");
}
}
}اختبر فهمك