תנאי הכתיבה לכללי האבטחה של 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 מתוך 10 קריאות הגישה שלה, ובבקשת הכתיבה האצווהית משתמשים ב-6 מתוך 20 קריאות הגישה שלה.

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

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

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

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

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

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

  • פונקציות יכולות להכיל רק הצהרת return אחת. הן לא יכולות להכיל לוגיקה נוספת. לדוגמה, הם לא יכולים להריץ לולאות או לבצע קריאות לשירותים חיצוניים.
  • הפונקציות יכולות לגשת באופן אוטומטי לפונקציות ולמשתנים מההיקף שבו הן מוגדרות. לדוגמה, לפונקציה שמוגדרת בתוך ההיקף service cloud.firestore יש גישה למשתנה resource ולפונקציות מובנות כמו get() ו-exists().
  • פונקציות יכולות לקרוא לפונקציות אחרות, אבל אסור להן לבצע חזרה חוזרת (recursion). עומק ה-call stack הכולל מוגבל ל-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 בודקים כל שאילתה מול התוצאה האפשרית שלה, ומשיבים על הבקשה בשלילה אם היא עלולה להחזיר מסמך שללקוח אין הרשאה לקרוא. השאילתות חייבות לעמוד באילוצים שהוגדרו בכללי האבטחה. מידע נוסף על שאילתות וכללי אבטחה זמין במאמר שליחת שאילתות לנתונים באופן מאובטח.

השלבים הבאים