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

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

לא חוקי: אילוצים Query אינם תואמים אילוצים כללי אבטחה

// This query will fail
db.collection("stories").get()

השאילתה תיכשל גם אם המשתמש הנוכחי למעשה הוא המחבר של כל story המסמך. הסיבה להתנהגות זו היא שכאשר ענן 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 סעיף שאילתא מול מערכת כללים, ענן 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 ruleset עם השינויים הבאים:

  • Ruleset מפריד הכלל לקרוא לתוך כללי get ואת list .
  • get מגביל כלל שליפת מסמכים יחידים על מסמכים ציבוריים או מסמכים שהמשתמש חבר.
  • list כלל חל אותן ההגבלות כמו get אלא לשאילתה. הוא גם בודק את מגבלת השאילתה ואז שולל כל שאילתה ללא מגבלה או עם מגבלה גדולה מ -10.
  • Ruleset מגדיר 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}/פוסטים/{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}/פוסטים/{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;
    }
  }
}

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

הצעדים הבאים