الاستعلام عن البيانات بأمان

تعتمد هذه الصفحة على المفاهيم الواردة في هيكلة قواعد الأمان وشروط الكتابة لقواعد الأمان لتوضيح كيفية تفاعل قواعد أمان Cloud Firestore مع طلبات البحث. حيث يلقي نظرة عن كثب على كيفية تأثير قواعد الأمان على طلبات البحث التي يمكنك كتابتها ويصف كيفية التأكد من أن طلبات البحث تستخدم القيود نفسها التي تفرضها قواعد الأمان. توضّح هذه الصفحة أيضًا كيفية كتابة قواعد الأمان للسماح بطلبات البحث أو رفضها استنادًا إلى خصائص طلب البحث مثل 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'; هو السطر الأول من مجموعة القواعد. تتطلب طلبات بحث مجموعة المجموعات سلوك حرف البدل التكراري الجديد {name=**} للإصدار 2 من قواعد الأمان.
  2. اكتب قاعدة لمجموعة المجموعات باستخدام match /{path=**}/[COLLECTION_ID]/{doc}.

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

/منتديات/{forumid}/post/{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 أعلاه:

/منتديات/{forumid}/post/{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
    }
  }
}

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