שאילתה מאובטחת לנתונים

דף זה מתבסס על המושגים בבניית כללי אבטחה וכתיבת תנאים לכללי אבטחה כדי להסביר כיצד כללי האבטחה של 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 בכללי האבטחה.

כלל האבטחה הבא משתמש במשתנים 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 :

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

עם הכללים האלה, לקוחות אינטרנט, אפל ו-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
    }
  }
}

הצעדים הבאים