תנאי הכתיבה לכללי האבטחה של Cloud Firestore

המדריך הזה מבוסס על המדריך למבנה כללי אבטחה שמדגים איך מוסיפים תנאים ל-Cloud Firestore Security Rules. אם אתם לא מכירים את היסודות של Cloud Firestore Security Rules, כדאי לעיין במדריך למתחילים.

אבן הבניין הראשית של Cloud Firestore Security Rules היא התנאי. תנאי הוא ביטוי בוליאני שקובע אם יש לאפשר או לדחות פעולה מסוימת. משתמשים בכללי אבטחה כדי לכתוב תנאים שבודקים את אימות המשתמשים, מאמתים נתונים נכנסים או אפילו ניגשים לחלקים אחרים במסד הנתונים.

אימות

אחד מהדפוסים הנפוצים ביותר של כללי אבטחה הוא שליטה בגישה לפי מצב האימות של המשתמש. לדוגמה, יכול להיות שתרצו לאפשר רק למשתמשים שמחוברים לכתוב נתונים:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

דפוס נפוץ נוסף הוא לוודא שהמשתמשים יכולים לקרוא ולכתוב רק את הנתונים שלהם:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

אם האפליקציה משתמשת באימות ב-Firebase או ב-Google Cloud Identity Platform, המשתנה request.auth מכיל את פרטי האימות של הלקוח שמבקש נתונים. למידע נוסף על request.auth, קראו את מאמרי העזרה.

אימות נתונים

באפליקציות רבות, פרטי בקרת הגישה מאוחסנים כשדות במסמכים במסד הנתונים. Cloud Firestore Security Rules יכול לאשר או לדחות גישה באופן דינמי על סמך נתוני המסמך:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

המשתנה resource מתייחס למסמך המבוקש, ו-resource.data הוא מפה של כל השדות והערכים שמאוחסנים במסמך. למידע נוסף על המשתנה resource, עיינו במסמכי העזרה.

כשכותבים נתונים, כדאי להשוות בין הנתונים הנכנסים לנתונים הקיימים. במקרה הזה, אם קבוצת הכללים מאפשרת את הכתיבה שבהמתנה, המשתנה request.resource מכיל את המצב העתידי של המסמך. בפעולות update שמשנים רק קבוצת משנה של שדות המסמך, אחרי הפעולה המשתנה request.resource מכיל את מצב המסמך בהמתנה. אפשר לבדוק את ערכי השדות ב-request.resource כדי למנוע עדכוני נתונים לא רצויים או לא עקביים:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

גישה למסמכים אחרים

באמצעות הפונקציות get() ו-exists(), כללי האבטחה יכולים להעריך בקשות נכנסות בהשוואה למסמכים אחרים במסד הנתונים. הפונקציות get() ו-exists() מצפות לנתיבי מסמכים שצוינו במלואם. כשמשתמשים במשתנים כדי ליצור נתיבים ל-get() ול-exists(), צריך לסמן משתנים באופן מפורש באמצעות התחביר $(variable).

בדוגמה הבאה, המשתנה database מתועד על ידי ביטוי ההתאמה match /databases/{database}/documents ומשמשים ליצירת הנתיב:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

לצורכי כתיבה, אפשר להשתמש בפונקציה getAfter() כדי לגשת למצב של מסמך אחרי סיום טרנזקציה או קבוצת קבצים, אבל לפני ביצוע עסקאות או אצווה. בדומה ל-get(), הפונקציה getAfter() לוקחת נתיב מסמך שצוין במלואו. אפשר להשתמש ב-getAfter() כדי להגדיר קבוצות של כתיבות שחייבות להתרחש יחד כטרנזקציה או כקבוצה.

הגבלות על שיחות גישה

יש מגבלה על קריאות גישה למסמכים בכל הערכה של קבוצת כללים:

  • 10 לבקשות של מסמך יחיד ולבקשות של שאילתות.
  • 20 לקריאות, לעסקאות ולכתובות באצווה של כמה מסמכים. המגבלה הקודמת של 10 חלה גם על כל פעולה.

    לדוגמה, נניח שיצרתם בקשת כתיבה באצווה עם 3 פעולות כתיבה, ושכללי האבטחה שלכם משתמשים ב-2 קריאות גישה למסמך כדי לאמת כל פעולת כתיבה. במקרה כזה, כל כתיבה משתמשת ב-2 מתוך 10 קריאות הגישה שלה, ובקשת הכתיבה באצווה משתמשת ב-6 מתוך 20 קריאות הגישה שלה.

אם חורגים מאחד מהגבולות האלה, מתקבלת הודעת שגיאה מסוג 'הרשאה נדחתה'. יכול להיות שחלק מהקריאות לגישה למסמכים יישמרו במטמון, וקריאות ששמורות במטמון לא נספרות במסגרת המגבלות.

להסבר מפורט על האופן שבו המגבלות האלה משפיעות על טרנזקציות ועל כתיבה באצווה, עיינו במדריך לאבטחת פעולות אטומיות.

שיחות גישה ותמחור

כשמשתמשים בפונקציות האלה, מתבצעת פעולת קריאה במסד הנתונים. כלומר, תחויבו על קריאת מסמכים גם אם הבקשה נדחתה על ידי הכללים. למידע ספציפי יותר על החיוב, ראו תמחור Cloud Firestore.

פונקציות מותאמות אישית

ככל שכללי האבטחה הופכים למורכבים יותר, כדאי לארוז קבוצות של תנאים בפונקציות שאפשר לעשות בהן שימוש חוזר בכל קבוצת הכללים. כללי האבטחה תומכים בפונקציות בהתאמה אישית. התחביר של פונקציות בהתאמה אישית דומה ל-JavaScript, אבל פונקציות של כללי אבטחה נכתבות בשפה ספציפית לדומיין שיש לה כמה מגבלות חשובות:

  • פונקציות יכולות להכיל רק הצהרת return אחת. הן לא יכולות להכיל לוגיקה נוספת. לדוגמה, הם לא יכולים להפעיל לולאות או להפעיל שירותים חיצוניים.
  • הפונקציות יכולות לגשת באופן אוטומטי לפונקציות ולמשתנים מההיקף שבו הן מוגדרות. לדוגמה, לפונקציה שמוגדרת בהיקף service cloud.firestore יש גישה למשתנה resource ולפונקציות מובנות כמו get() ו-exists().
  • פונקציות יכולות לקרוא לפונקציות אחרות, אבל אסור להן לבצע חזרה חוזרת (recursion). העומק הכולל של מקבץ הקריאות מוגבל ל-10.
  • בגרסה v2 של הכללים, פונקציות יכולות להגדיר משתנים באמצעות מילת המפתח let. פונקציות יכולות לכלול עד 10 קישורי let, אבל הן חייבות להסתיים בהצהרת return.

פונקציה מוגדרת באמצעות מילת המפתח function, והיא יכולה לקבל אפס ארגומנטים או יותר. לדוגמה, יכול להיות שתרצו לשלב בפונקציה אחת את שני סוגי התנאים שמופיעים בדוגמאות שלמעלה:

service cloud.firestore {
  match /databases/{database}/documents {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }

    match /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

שימוש בפונקציות בכללי האבטחה מאפשר לכם לשמור עליהם בקלות רבה יותר ככל שהם נעשים מורכבים יותר.

כללים הם לא מסננים

אחרי שאתם מאבטחים את הנתונים ומתחילים לכתוב שאילתות, חשוב לזכור שכללי האבטחה הם לא מסננים. אי אפשר לכתוב שאילתה על כל המסמכים באוסף ולצפות ש-Cloud Firestore יחזיר רק את המסמכים שללקוח הנוכחי יש הרשאת גישה אליהם.

לדוגמה, ניקח את כלל האבטחה הבא:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

נדחתה: הכלל הזה דוחה את השאילתה הבאה כי קבוצת התוצאות יכולה לכלול מסמכים שבהם visibility לא public:

אינטרנט
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

מותר: הכלל הזה מאפשר את השאילתה הבאה כי התנאי where("visibility", "==", "public") מבטיח שקבוצת התוצאות עומדת בתנאי הכלל:

אינטרנט
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

כללי האבטחה של Cloud Firestore מעריכים כל שאילתה ביחס לתוצאה הפוטנציאלית שלה, והבקשה נכשלת אם היא יכולה להחזיר מסמך שללקוח אין הרשאה לקרוא. השאילתות חייבות לעמוד באילוצים שהוגדרו בכללי האבטחה. מידע נוסף על שאילתות וכללי אבטחה זמין במאמר שליחת שאילתות לנתונים באופן מאובטח.

השלבים הבאים