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

تستند هذه الصفحة إلى المفاهيم الواردة في تنظيم قواعد الأمان و كتابة الشروط لقواعد الأمان لتوضيح كيفية تفاعل 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 لضبط الحقل authorعلى رقم تعريف المستخدم الذي أنشأ المستند. تضبط "مصادقة Firebase" أيضًا المتغيّر 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'; هو السطر الأول من مجموعة القواعد. تتطلب طلبات البحث عن مجموعة المجموعات السلوك الجديد للبطاقة بدلًا من حرف واحد في قواعد الأمان الإصدار 2.{name=**}
  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
    }
  }
}

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