طلب البحث عن البيانات بأمان

تستند هذه الصفحة إلى المفاهيم الواردة في تنظيم قواعد الأمان وكتابة شروط لقواعد الأمان لتوضيح كيفية تفاعل Cloud Firestore Security Rules مع طلبات البحث. ويشرح هذا الدليل بالتفصيل كيف تؤثّر قواعد الأمان في طلبات البحث التي يمكنك كتابتها، كما يوضّح كيفية التأكّد من أنّ طلبات البحث تستخدم القيود نفسها التي تستخدمها قواعد الأمان. توضّح هذه الصفحة أيضًا كيفية كتابة قواعد الأمان للسماح بالطلبات أو رفضها استنادًا إلى خصائص الطلبات، مثل limit وorderBy.

القواعد ليست فلاتر

عند كتابة طلبات بحث لاسترداد المستندات، تذكَّر أنّ قواعد الأمان ليست فلاتر، بل إنّ طلبات البحث إما أن تكون شاملة أو لا شيء. لتوفير الوقت والموارد، تقيِّم Cloud Firestore طلب بحث مقابل مجموعة النتائج المحتملة بدلاً من قيم الحقول الفعلية لجميع مستنداتك. إذا كان طلب البحث قد يعرض مستندات لا يملك العميل إذنًا بقراءتها، سيفشل الطلب بأكمله.

طلبات البحث وقواعد الأمان

كما توضّح الأمثلة أدناه، يجب كتابة طلبات البحث بما يتناسب مع قيود قواعد الأمان.

تأمين المستندات والاستعلام عنها استنادًا إلى auth.uid

يوضّح المثال التالي كيفية كتابة طلب بحث لاسترداد المستندات المحمية بموجب قاعدة أمان. لنفترض أنّ لديك قاعدة بيانات تحتوي على مجموعة من story المستندات:

/stories/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

بالإضافة إلى الحقلَين title وcontent، يخزّن كل مستند الحقلَين author وpublished لاستخدامهما في التحكّم في الوصول. تفترض هذه الأمثلة أنّ التطبيق يستخدم Firebase Authentication لضبط الحقل author على معرّف UID الخاص بالمستخدم الذي أنشأ المستند. تعبئ خدمة Firebase Authentication أيضًا المتغيّر request.auth في قواعد الأمان.

تستخدِم قاعدة الأمان التالية المتغيّرَين request.auth وresource.data لحظر إذن القراءة والكتابة لكل story على مؤلفه:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

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

غير صالح: لا تتطابق قيود طلب البحث مع قيود قواعد الأمان

// This query will fail
db.collection("stories").get()

يفشل طلب البحث حتى إذا كان المستخدم الحالي هو مؤلف كل مستند story. ويعود سبب هذا السلوك إلى أنّه عندما تطبّق Cloud Firestore قواعد الأمان، فإنّها تقيّم طلب البحث مقابل مجموعة النتائج المحتملة، وليس مقابل السمات الفعلية للمستندات في قاعدة البيانات. إذا كان طلب البحث من المحتمل أن يتضمّن مستندات تنتهك قواعد الأمان، سيتعذّر تنفيذ طلب البحث.

في المقابل، ينجح الاستعلام التالي لأنّه يتضمّن القيد نفسه على الحقل author كما في قواعد الأمان:

صالح: تتطابق قيود طلب البحث مع قيود قواعد الأمان

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

تأمين المستندات والبحث فيها استنادًا إلى حقل

لتوضيح التفاعل بين طلبات البحث والقواعد، توسّع قواعد الأمان أدناه إذن الوصول للقراءة إلى المجموعة stories للسماح لأي مستخدم بقراءة مستندات story حيث تم ضبط الحقل published على true.

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

يجب أن يتضمّن طلب البحث عن الصفحات المنشورة القيود نفسها التي تتضمّنها قواعد الأمان:

db.collection("stories").where("published", "==", true).get()

يضمن قيد طلب البحث .where("published", "==", true) أنّ resource.data.published هو true لأي نتيجة. لذلك، يستوفي هذا الطلب شروط قواعد الأمان ويُسمح له بقراءة البيانات.

OR طلب بحث

عند تقييم طلب بحث منطقي OR (or أو in أو array-contains-any) مقابل مجموعة قواعد، يقيّم Cloud Firestore كل قيمة مقارنة بشكل منفصل. يجب أن تستوفي كل قيمة مقارنة قيود قاعدة الأمان. على سبيل المثال، بالنسبة إلى القاعدة التالية:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

غير صالح: لا يضمن طلب البحث أن يكون x > 5 متوفّرًا لجميع المستندات المحتملة

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

صالح: يضمن طلب البحث أنّ x > 5 لجميع المستندات المحتملة

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [6, 42, 99, 105, 200])
    )

تقييم القيود المفروضة على طلبات البحث

يمكن لقواعد الأمان أيضًا قبول الطلبات أو رفضها استنادًا إلى قيودها. يحتوي المتغيّر request.query على السمات limit وoffset وorderBy الخاصة بطلب بحث. على سبيل المثال، يمكن أن ترفض قواعد الأمان أي طلب بحث لا يحدّ من الحد الأقصى لعدد المستندات التي يتم استردادها إلى نطاق معيّن:

allow list: if request.query.limit <= 10;

توضّح مجموعة القواعد التالية كيفية كتابة قواعد أمان تقيّم القيود المفروضة على طلبات البحث. يوسّع هذا المثال مجموعة القواعد السابقة stories من خلال التغييرات التالية:

  • تفصل مجموعة القواعد قاعدة القراءة إلى قواعد خاصة بـ get وlist.
  • تقيّد القاعدة get عملية استرداد المستندات الفردية بالمستندات العلنية أو المستندات التي أنشأها المستخدم.
  • تفرض القاعدة list القيود نفسها التي تفرضها get ولكن على طلبات البحث. ويتحقّق أيضًا من الحدّ الأقصى لعدد الطلبات، ثم يرفض أي طلب بدون حدّ أو بحدّ أكبر من 10.
  • تحدّد مجموعة القواعد الدالة authorOrPublished() لتجنُّب تكرار الرمز البرمجي.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

طلبات البحث عن مجموعات البيانات وقواعد الأمان

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

تأمين المستندات والاستعلام عنها استنادًا إلى مجموعات المستندات

في قواعد الأمان، يجب السماح صراحةً بطلبات البحث عن مجموعة البيانات من خلال كتابة قاعدة لمجموعة البيانات:

  1. تأكَّد من أنّ rules_version = '2'; هو السطر الأول في مجموعة القواعد. تتطلّب طلبات البحث الخاصة بمجموعات المجموعات استخدام السلوك الجديد الخاص بحرف البدل العودي {name=**} في الإصدار 2 من قواعد الأمان.
  2. اكتب قاعدة لمجموعة مجموعتك باستخدام match /{path=**}/[COLLECTION_ID]/{doc}.

على سبيل المثال، لنفترض أنّ هناك منتدى منظَّمًا في forum مستندات تحتوي على مجموعات فرعية posts:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

في هذا التطبيق، نتيح للمالكين تعديل المشاركات، كما نتيح للمستخدمين الذين تم التحقّق من هويتهم قراءتها:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

يمكن لأي مستخدم تمت مصادقته استرداد المشاركات من أي منتدى واحد:

db.collection("forums/technology/posts").get()

ولكن ماذا لو أردت أن تعرض للمستخدم الحالي مشاركاته في جميع المنتديات؟ يمكنك استخدام طلب بحث عن مجموعة مجموعات لاسترداد النتائج من جميع مجموعات posts:

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

في قواعد الأمان، يجب السماح بهذا الطلب من خلال كتابة قاعدة قراءة أو قائمة لمجموعة المجموعة posts:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

يُرجى العِلم أنّ هذه القواعد ستنطبق على جميع المجموعات التي تحمل المعرّف posts، بغض النظر عن التسلسل الهرمي. على سبيل المثال، تنطبق هذه القواعد على جميع المجموعات التالية:posts

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

طلبات البحث الآمنة في مجموعات التجميع استنادًا إلى حقل

مثل طلبات البحث التي تتضمّن عملية جمع واحدة، يجب أن تستوفي طلبات البحث التي تتضمّن مجموعة جمع أيضًا القيود التي تحدّدها قواعد الأمان. على سبيل المثال، يمكننا إضافة حقل published إلى كل مشاركة في المنتدى، كما فعلنا في المثال stories أعلاه:

/forums/{forumid}/posts/{postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

يمكننا بعد ذلك كتابة قواعد لمجموعة المجموعة posts استنادًا إلى الحالة published والمشاركة author:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

باستخدام هذه القواعد، يمكن لبرامج Web وApple وAndroid إجراء طلبات البحث التالية:

  • يمكن لأي مستخدم استرداد المشاركات المنشورة في منتدى:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • يمكن لأي مستخدم استرداد مشاركات مؤلف منشورة في جميع المنتديات:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • يمكن للمؤلفين استرداد جميع مشاركاتهم المنشورة وغير المنشورة في جميع المنتديات باتّباع الخطوات التالية:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

تأمين المستندات والاستعلام عنها استنادًا إلى مجموعة المستندات ومسار المستند

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

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

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

لاحظ الحقل user. مع أنّنا نعرف المستخدم الذي يملك مستند transaction من مسار المستند، نكرّر هذه المعلومات في كل مستند transaction لأنّ ذلك يتيح لنا إجراء ما يلي:

  • اكتب طلبات بحث خاصة بمجموعات المجموعات محصورة على المستندات التي تتضمّن /users/{userid} معيّنًا في مسار المستند. على سبيل المثال:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • فرض هذا القيد على جميع طلبات البحث في مجموعة transactions لكي لا يتمكّن مستخدم من استرداد مستندات transaction لمستخدم آخر

نفرض هذا القيد في قواعد الأمان الخاصة بنا ونضمِّن عملية التحقّق من صحة البيانات للحقل user:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

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