الاستعلام عن البيانات بشكل آمن

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

القواعد ليست مرشحات

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

الاستعلامات وقواعد الأمان

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

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

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

/قصص/{storyid}

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

بالإضافة إلى حقلي title content ، يقوم كل مستند بتخزين author والحقول published لاستخدامها في التحكم في الوصول. تفترض هذه الأمثلة أن التطبيق يستخدم مصادقة Firebase لتعيين حقل author إلى UID الخاص بالمستخدم الذي أنشأ المستند. تقوم مصادقة 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}/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 أعلاه:

/المنتديات/{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()
    

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

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

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

/المستخدمين/{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
    }
  }
}

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