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

تعتمد هذه الصفحة على المفاهيم في هيكلة قواعد الأمان وشروط الكتابة لقواعد الأمان لشرح كيفية تفاعل قواعد أمان 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 على UID للمستخدم الذي أنشأ المستند. تملأ مصادقة Firebase أيضًا متغير request.auth في قواعد الأمان.

تستخدم قاعدة الأمان التالية resource.data 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 لأي نتيجة. لذلك ، يفي هذا الاستعلام بقواعد الأمان ويسمح له بقراءة البيانات.

in array-contains-any استعلامات

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

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

غير صالح : الاستعلام لا يضمن أن x > 5 لجميع المستندات المحتملة

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

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

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

تقييم القيود على الاستعلامات

يمكن لقواعد الأمان الخاصة بك أيضًا قبول أو رفض الاستعلامات بناءً على قيودها. يحتوي المتغير 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 بناءً على حالة النشر author published :

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} / Transaction / {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)
    
  • قم بفرض هذا التقييد على كافة الاستعلامات في مجموعة مجموعة transaction transactions بمستخدم آخر.

نحن نفرض هذا التقييد في قواعد الأمان الخاصة بنا ونقوم بتضمين التحقق من صحة البيانات لحقل 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
    }
  }
}

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