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

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

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

// 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()

تأمين المستندات وإجراء طلب بحث عنها استنادًا إلى حقل

لمزيد من توضيح التفاعل بين طلبات البحث والقواعد، توسّع قواعد security أدناه إذن الوصول للقراءة إلى مجموعة 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;
    }

  }
}

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

يتمّ تلقائيًا حصر طلبات البحث في مجموعة واحدة، ولا يتمّ استرداد النتائج إلا من تلك المجموعة. باستخدام طلبات بحث مجموعات المجموعات، يمكنك retrieving results from a collection group consisting of all collections with the same ID. يوضّح هذا القسم كيفية تأمين طلبات بحث مجموعات المجموعات باستخدام قواعد الأمان.

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

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

  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;
    }
  }
}

باستخدام هذه القواعد، يمكن لعملاء الويب و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
    }
  }
}

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